Merge branch 'develop'
This commit is contained in:
commit
3f0f60252a
|
@ -18,4 +18,4 @@ jobs:
|
|||
bump-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}]'
|
||||
# release-as: 0.4.0
|
||||
release-as: 0.5.0
|
|
@ -2,8 +2,8 @@ package activity
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/times"
|
||||
"reflect"
|
||||
"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)) {
|
||||
controller.mutex.RLock()
|
||||
entities := hash.Copy(controller.entityData[activityId])
|
||||
entities := collection.CloneMap(controller.entityData[activityId])
|
||||
controller.mutex.RUnlock()
|
||||
for entityId, data := range entities {
|
||||
handler(controller.t, activityId, entityId, data)
|
||||
|
|
|
@ -2,9 +2,10 @@ package activity
|
|||
|
||||
import (
|
||||
"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/log"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"github.com/kercylan98/minotaur/utils/timer"
|
||||
"github.com/kercylan98/minotaur/utils/times"
|
||||
"reflect"
|
||||
|
@ -21,28 +22,28 @@ type (
|
|||
)
|
||||
|
||||
var (
|
||||
upcomingEventHandlers map[any]*slice.Priority[func(activityId any)] // 即将开始的活动事件处理器
|
||||
startedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动开始事件处理器
|
||||
endedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束事件处理器
|
||||
extShowStartedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束后延长展示开始事件处理器
|
||||
extShowEndedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束后延长展示结束事件处理器
|
||||
newDayEventHandlers map[any]*slice.Priority[func(activityId any)] // 新的一天事件处理器
|
||||
upcomingEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 即将开始的活动事件处理器
|
||||
startedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动开始事件处理器
|
||||
endedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束事件处理器
|
||||
extShowStartedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束后延长展示开始事件处理器
|
||||
extShowEndedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束后延长展示结束事件处理器
|
||||
newDayEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 新的一天事件处理器
|
||||
)
|
||||
|
||||
func init() {
|
||||
upcomingEventHandlers = make(map[any]*slice.Priority[func(activityId any)])
|
||||
startedEventHandlers = make(map[any]*slice.Priority[func(activityId any)])
|
||||
endedEventHandlers = make(map[any]*slice.Priority[func(activityId any)])
|
||||
extShowStartedEventHandlers = make(map[any]*slice.Priority[func(activityId any)])
|
||||
extShowEndedEventHandlers = make(map[any]*slice.Priority[func(activityId any)])
|
||||
newDayEventHandlers = make(map[any]*slice.Priority[func(activityId any)])
|
||||
upcomingEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||
startedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||
endedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||
extShowStartedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||
extShowEndedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||
newDayEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
|
||||
}
|
||||
|
||||
// RegUpcomingEvent 注册即将开始的活动事件处理器
|
||||
func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler UpcomingEventHandler[ID], priority ...int) {
|
||||
handlers, exist := upcomingEventHandlers[activityType]
|
||||
if !exist {
|
||||
handlers = slice.NewPriority[func(activityId any)]()
|
||||
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||
upcomingEventHandlers[activityType] = handlers
|
||||
}
|
||||
handlers.Append(func(activityId any) {
|
||||
|
@ -50,7 +51,7 @@ func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler Upcomin
|
|||
return
|
||||
}
|
||||
handler(activityId.(ID))
|
||||
}, slice.GetValue(priority, 0))
|
||||
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
handlers, exist := startedEventHandlers[activityType]
|
||||
if !exist {
|
||||
handlers = slice.NewPriority[func(activityId any)]()
|
||||
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||
startedEventHandlers[activityType] = handlers
|
||||
}
|
||||
handlers.Append(func(activityId any) {
|
||||
|
@ -83,7 +84,7 @@ func RegStartedEvent[Type, ID generic.Basic](activityType Type, handler StartedE
|
|||
return
|
||||
}
|
||||
handler(activityId.(ID))
|
||||
}, slice.GetValue(priority, 0))
|
||||
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
handlers, exist := endedEventHandlers[activityType]
|
||||
if !exist {
|
||||
handlers = slice.NewPriority[func(activityId any)]()
|
||||
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||
endedEventHandlers[activityType] = handlers
|
||||
}
|
||||
handlers.Append(func(activityId any) {
|
||||
|
@ -124,7 +125,7 @@ func RegEndedEvent[Type, ID generic.Basic](activityType Type, handler EndedEvent
|
|||
return
|
||||
}
|
||||
handler(activityId.(ID))
|
||||
}, slice.GetValue(priority, 0))
|
||||
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
handlers, exist := extShowStartedEventHandlers[activityType]
|
||||
if !exist {
|
||||
handlers = slice.NewPriority[func(activityId any)]()
|
||||
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||
extShowStartedEventHandlers[activityType] = handlers
|
||||
}
|
||||
handlers.Append(func(activityId any) {
|
||||
|
@ -157,7 +158,7 @@ func RegExtendedShowStartedEvent[Type, ID generic.Basic](activityType Type, hand
|
|||
return
|
||||
}
|
||||
handler(activityId.(ID))
|
||||
}, slice.GetValue(priority, 0))
|
||||
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
handlers, exist := extShowEndedEventHandlers[activityType]
|
||||
if !exist {
|
||||
handlers = slice.NewPriority[func(activityId any)]()
|
||||
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||
extShowEndedEventHandlers[activityType] = handlers
|
||||
}
|
||||
handlers.Append(func(activityId any) {
|
||||
|
@ -190,7 +191,7 @@ func RegExtendedShowEndedEvent[Type, ID generic.Basic](activityType Type, handle
|
|||
return
|
||||
}
|
||||
handler(activityId.(ID))
|
||||
}, slice.GetValue(priority, 0))
|
||||
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
handlers, exist := newDayEventHandlers[activityType]
|
||||
if !exist {
|
||||
handlers = slice.NewPriority[func(activityId any)]()
|
||||
handlers = listings.NewPrioritySlice[func(activityId any)]()
|
||||
newDayEventHandlers[activityType] = handlers
|
||||
}
|
||||
handlers.Append(func(activityId any) {
|
||||
|
@ -223,7 +224,7 @@ func RegNewDayEvent[Type, ID generic.Basic](activityType Type, handler NewDayEve
|
|||
return
|
||||
}
|
||||
handler(activityId.(ID))
|
||||
}, slice.GetValue(priority, 0))
|
||||
}, collection.FindFirstOrDefaultInSlice(priority, 0))
|
||||
}
|
||||
|
||||
// OnNewDayEvent 新的一天事件
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package space
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -201,7 +200,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetNotEmptySeat() []in
|
|||
// GetEmptySeat 获取空座位
|
||||
// - 空座位需要在有对象离开座位后才可能出现
|
||||
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEmptySeat() []int {
|
||||
return slice.Copy(rc.vacancy)
|
||||
return collection.CloneSlice(rc.vacancy)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
rc.entitiesRWMutex.RLock()
|
||||
defer rc.entitiesRWMutex.RUnlock()
|
||||
return hash.Copy(rc.entities)
|
||||
return collection.CloneMap(rc.entities)
|
||||
}
|
||||
|
||||
// HasEntity 判断是否有实体
|
||||
|
@ -321,7 +320,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityExist(id Enti
|
|||
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityIDs() []EntityID {
|
||||
rc.entitiesRWMutex.RLock()
|
||||
defer rc.entitiesRWMutex.RUnlock()
|
||||
return hash.KeyToSlice(rc.entities)
|
||||
return collection.ConvertMapKeysToSlice(rc.entities)
|
||||
}
|
||||
|
||||
// GetEntityCount 获取实体数量
|
||||
|
@ -454,7 +453,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRoomID() RoomID {
|
|||
// Broadcast 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理
|
||||
func (rc *RoomController[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) {
|
||||
rc.entitiesRWMutex.RLock()
|
||||
entities := hash.Copy(rc.entities)
|
||||
entities := collection.CloneMap(rc.entities)
|
||||
rc.entitiesRWMutex.RUnlock()
|
||||
for _, entity := range entities {
|
||||
for _, condition := range conditions {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package space
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"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] {
|
||||
rm.roomsRWMutex.RLock()
|
||||
defer rm.roomsRWMutex.RUnlock()
|
||||
return hash.Copy(rm.rooms)
|
||||
return collection.CloneMap(rm.rooms)
|
||||
}
|
||||
|
||||
// GetRoomCount 获取房间管理器接管的房间数量
|
||||
|
@ -68,13 +68,13 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomCount() int {
|
|||
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomIDs() []RoomID {
|
||||
rm.roomsRWMutex.RLock()
|
||||
defer rm.roomsRWMutex.RUnlock()
|
||||
return hash.KeyToSlice(rm.rooms)
|
||||
return collection.ConvertMapKeysToSlice(rm.rooms)
|
||||
}
|
||||
|
||||
// HasEntity 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false
|
||||
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId EntityID) bool {
|
||||
rm.roomsRWMutex.RLock()
|
||||
rooms := hash.Copy(rm.rooms)
|
||||
rooms := collection.CloneMap(rm.rooms)
|
||||
rm.roomsRWMutex.RUnlock()
|
||||
for _, room := range rooms {
|
||||
if room.HasEntity(entityId) {
|
||||
|
@ -88,7 +88,7 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId Entity
|
|||
// - 由于一个对象可能在多个房间中,因此返回值为 map 类型
|
||||
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] {
|
||||
rm.roomsRWMutex.RLock()
|
||||
rooms := hash.Copy(rm.rooms)
|
||||
rooms := collection.CloneMap(rm.rooms)
|
||||
rm.roomsRWMutex.RUnlock()
|
||||
var result = make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room])
|
||||
for id, room := range rooms {
|
||||
|
@ -102,7 +102,7 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId E
|
|||
// Broadcast 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法
|
||||
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) {
|
||||
rm.roomsRWMutex.RLock()
|
||||
rooms := hash.Copy(rm.rooms)
|
||||
rooms := collection.CloneMap(rm.rooms)
|
||||
rm.roomsRWMutex.RUnlock()
|
||||
for _, room := range rooms {
|
||||
room.Broadcast(handler, conditions...)
|
||||
|
|
|
@ -37,7 +37,7 @@ func TestCond(t *testing.T) {
|
|||
|
||||
player := &Player{
|
||||
tasks: map[string][]*Task{
|
||||
task.Type: []*Task{task},
|
||||
task.Type: {task},
|
||||
},
|
||||
}
|
||||
OnRefreshTaskCounterEvent(task.Type, player, 1)
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"github.com/kercylan98/minotaur/planner/pce"
|
||||
"github.com/kercylan98/minotaur/planner/pce/cs"
|
||||
"github.com/kercylan98/minotaur/planner/pce/tmpls"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/file"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/str"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tealeg/xlsx"
|
||||
|
@ -64,7 +64,7 @@ func init() {
|
|||
var exporter = pce.NewExporter()
|
||||
loader := pce.NewLoader(pce.GetFields())
|
||||
|
||||
excludes := hash.ToMapBool(str.SplitTrimSpace(exclude, ","))
|
||||
excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ","))
|
||||
for _, xlsxFile := range xlsxFiles {
|
||||
xf, err := xlsx.OpenFile(xlsxFile)
|
||||
if err != nil {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"github.com/kercylan98/minotaur/planner/pce"
|
||||
"github.com/kercylan98/minotaur/planner/pce/cs"
|
||||
"github.com/kercylan98/minotaur/planner/pce/tmpls"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/file"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/str"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tealeg/xlsx"
|
||||
|
@ -61,7 +61,7 @@ func init() {
|
|||
var exporter = pce.NewExporter()
|
||||
loader := pce.NewLoader(pce.GetFields())
|
||||
|
||||
excludes := hash.ToMapBool(str.SplitTrimSpace(exclude, ","))
|
||||
excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ","))
|
||||
for _, xlsxFile := range xlsxFiles {
|
||||
xf, err := xlsx.OpenFile(xlsxFile)
|
||||
if err != nil {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"github.com/kercylan98/minotaur/planner/pce"
|
||||
"github.com/kercylan98/minotaur/planner/pce/cs"
|
||||
"github.com/kercylan98/minotaur/planner/pce/tmpls"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/file"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/str"
|
||||
"github.com/tealeg/xlsx"
|
||||
"os"
|
||||
|
@ -61,7 +61,7 @@ func TestExecute(t *testing.T) {
|
|||
var exporter = pce.NewExporter()
|
||||
loader := pce.NewLoader(pce.GetFields())
|
||||
|
||||
excludes := hash.ToMapBool(str.SplitTrimSpace(exclude, ","))
|
||||
excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ","))
|
||||
for _, xlsxFile := range xlsxFiles {
|
||||
xf, err := xlsx.OpenFile(xlsxFile)
|
||||
if err != nil {
|
||||
|
|
Binary file not shown.
|
@ -2,7 +2,7 @@ package pce
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/str"
|
||||
"strings"
|
||||
)
|
||||
|
@ -96,7 +96,7 @@ func (slf *TmplField) handleSlice(fieldName, fieldType string, fields map[string
|
|||
}
|
||||
slf.slice = true
|
||||
t := strings.TrimPrefix(fieldType, "[]")
|
||||
if hash.Exist(fields, t) {
|
||||
if collection.KeyInMap(fields, t) {
|
||||
slf.Struct = nil
|
||||
slf.Type = t
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package pce
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
// TmplStruct 模板结构
|
||||
|
@ -19,7 +19,7 @@ func (slf *TmplStruct) addField(parent, name, desc, fieldType string, fields map
|
|||
Desc: desc,
|
||||
Type: fieldType,
|
||||
}
|
||||
if !hash.Exist(fields, fieldType) {
|
||||
if !collection.KeyInMap(fields, fieldType) {
|
||||
field.setStruct(parent, name, desc, fieldType, fields)
|
||||
} else {
|
||||
field.Type = GetFieldGolangType(fields[fieldType])
|
||||
|
|
|
@ -27,7 +27,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) {
|
|||
import (
|
||||
jsonIter "github.com/json-iterator/go"
|
||||
"github.com/kercylan98/minotaur/utils/log"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
@ -196,7 +196,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) {
|
|||
func GetConfigs() map[Sign]any {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return hash.Copy(*configs.Load())
|
||||
return collection.CloneMap(*configs.Load())
|
||||
}
|
||||
|
||||
// GetConfigSigns 获取所有配置的标识
|
||||
|
@ -208,7 +208,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) {
|
|||
func Sync(handle func(configs map[Sign]any)) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
handle(hash.Copy(*configs.Load()))
|
||||
handle(collection.CloneMap(*configs.Load()))
|
||||
}
|
||||
|
||||
{{- range .Templates}}
|
||||
|
|
|
@ -45,5 +45,5 @@ func TestNewBot(t *testing.T) {
|
|||
bot.SendPacket([]byte("hello"))
|
||||
})
|
||||
|
||||
srv.Run(":9600")
|
||||
_ = srv.Run(":9600")
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/server/writeloop"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -30,7 +30,7 @@ type Client struct {
|
|||
core Core
|
||||
mutex sync.Mutex
|
||||
closed bool // 是否已关闭
|
||||
pool *concurrent.Pool[*Packet] // 数据包缓冲池
|
||||
pool *hub.ObjectPool[*Packet] // 数据包缓冲池
|
||||
loop *writeloop.Channel[*Packet] // 写入循环
|
||||
loopBufferSize int // 写入循环缓冲区大小
|
||||
block chan struct{} // 以阻塞方式运行
|
||||
|
@ -73,7 +73,7 @@ func (slf *Client) RunByBufferSize(size int, block ...bool) error {
|
|||
return err
|
||||
}
|
||||
slf.closed = false
|
||||
slf.pool = concurrent.NewPool[Packet](func() *Packet {
|
||||
slf.pool = hub.NewObjectPool[Packet](func() *Packet {
|
||||
return new(Packet)
|
||||
}, func(data *Packet) {
|
||||
data.wst = 0
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/kercylan98/minotaur/server/writeloop"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
"github.com/kercylan98/minotaur/utils/log"
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
"github.com/kercylan98/minotaur/utils/timer"
|
||||
|
@ -125,7 +125,7 @@ type connection struct {
|
|||
gw func(packet []byte)
|
||||
data map[any]any
|
||||
closed bool
|
||||
pool *concurrent.Pool[*connPacket]
|
||||
pool *hub.ObjectPool[*connPacket]
|
||||
loop writeloop.WriteLoop[*connPacket]
|
||||
mu sync.Mutex
|
||||
openTime time.Time
|
||||
|
@ -203,7 +203,7 @@ func (slf *Conn) GetData(key any) any {
|
|||
|
||||
// ViewData 查看只读的连接数据
|
||||
func (slf *Conn) ViewData() map[any]any {
|
||||
return hash.Copy(slf.data)
|
||||
return collection.CloneMap(slf.data)
|
||||
}
|
||||
|
||||
// SetMessageData 设置消息数据,该数据将在消息处理完成后释放
|
||||
|
@ -286,7 +286,7 @@ func (slf *Conn) init() {
|
|||
}))
|
||||
}
|
||||
}
|
||||
slf.pool = concurrent.NewPool[connPacket](
|
||||
slf.pool = hub.NewObjectPool[connPacket](
|
||||
func() *connPacket {
|
||||
return &connPacket{}
|
||||
}, func(data *connPacket) {
|
||||
|
@ -358,11 +358,6 @@ func (slf *Conn) Close(err ...error) {
|
|||
}
|
||||
if slf.ticker != nil {
|
||||
slf.ticker.Release()
|
||||
}
|
||||
if !slf.server.runtime.disableAutomaticReleaseShunt {
|
||||
slf.server.releaseDispatcher(slf)
|
||||
} else {
|
||||
|
||||
}
|
||||
slf.loop.Close()
|
||||
slf.mu.Unlock()
|
||||
|
|
|
@ -2,11 +2,11 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type hub struct {
|
||||
type connMgr struct {
|
||||
connections map[string]*Conn // 所有连接
|
||||
|
||||
register chan *Conn // 注册连接
|
||||
|
@ -26,12 +26,12 @@ type hubBroadcast struct {
|
|||
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.register = make(chan *Conn, DefaultConnHubBufferSize)
|
||||
h.unregister = make(chan string, DefaultConnHubBufferSize)
|
||||
h.broadcast = make(chan hubBroadcast, DefaultConnHubBufferSize)
|
||||
go func(ctx context.Context, h *hub) {
|
||||
go func(ctx context.Context, h *connMgr) {
|
||||
for {
|
||||
select {
|
||||
case conn := <-h.register:
|
||||
|
@ -54,7 +54,7 @@ func (h *hub) run(ctx context.Context) {
|
|||
}
|
||||
|
||||
// registerConn 注册连接
|
||||
func (h *hub) registerConn(conn *Conn) {
|
||||
func (h *connMgr) registerConn(conn *Conn) {
|
||||
select {
|
||||
case h.register <- conn:
|
||||
default:
|
||||
|
@ -63,7 +63,7 @@ func (h *hub) registerConn(conn *Conn) {
|
|||
}
|
||||
|
||||
// unregisterConn 注销连接
|
||||
func (h *hub) unregisterConn(id string) {
|
||||
func (h *connMgr) unregisterConn(id string) {
|
||||
select {
|
||||
case h.unregister <- id:
|
||||
default:
|
||||
|
@ -72,21 +72,21 @@ func (h *hub) unregisterConn(id string) {
|
|||
}
|
||||
|
||||
// GetOnlineCount 获取在线人数
|
||||
func (h *hub) GetOnlineCount() int {
|
||||
func (h *connMgr) GetOnlineCount() int {
|
||||
h.chanMutex.RLock()
|
||||
defer h.chanMutex.RUnlock()
|
||||
return h.onlineCount
|
||||
}
|
||||
|
||||
// GetOnlineBotCount 获取在线机器人数量
|
||||
func (h *hub) GetOnlineBotCount() int {
|
||||
func (h *connMgr) GetOnlineBotCount() int {
|
||||
h.chanMutex.RLock()
|
||||
defer h.chanMutex.RUnlock()
|
||||
return h.botCount
|
||||
}
|
||||
|
||||
// IsOnline 是否在线
|
||||
func (h *hub) IsOnline(id string) bool {
|
||||
func (h *connMgr) IsOnline(id string) bool {
|
||||
h.chanMutex.RLock()
|
||||
_, exist := h.connections[id]
|
||||
h.chanMutex.RUnlock()
|
||||
|
@ -94,15 +94,15 @@ func (h *hub) IsOnline(id string) bool {
|
|||
}
|
||||
|
||||
// GetOnlineAll 获取所有在线连接
|
||||
func (h *hub) GetOnlineAll() map[string]*Conn {
|
||||
func (h *connMgr) GetOnlineAll() map[string]*Conn {
|
||||
h.chanMutex.RLock()
|
||||
cop := hash.Copy(h.connections)
|
||||
cop := collection.CloneMap(h.connections)
|
||||
h.chanMutex.RUnlock()
|
||||
return cop
|
||||
}
|
||||
|
||||
// GetOnline 获取在线连接
|
||||
func (h *hub) GetOnline(id string) *Conn {
|
||||
func (h *connMgr) GetOnline(id string) *Conn {
|
||||
h.chanMutex.RLock()
|
||||
conn := h.connections[id]
|
||||
h.chanMutex.RUnlock()
|
||||
|
@ -110,7 +110,7 @@ func (h *hub) GetOnline(id string) *Conn {
|
|||
}
|
||||
|
||||
// CloseConn 关闭连接
|
||||
func (h *hub) CloseConn(id string) {
|
||||
func (h *connMgr) CloseConn(id string) {
|
||||
h.chanMutex.RLock()
|
||||
conn := h.connections[id]
|
||||
h.chanMutex.RUnlock()
|
||||
|
@ -120,7 +120,7 @@ func (h *hub) CloseConn(id string) {
|
|||
}
|
||||
|
||||
// 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{
|
||||
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()
|
||||
if h.closed {
|
||||
conn.Close()
|
||||
|
@ -148,7 +148,7 @@ func (h *hub) onRegister(conn *Conn) {
|
|||
h.chanMutex.Unlock()
|
||||
}
|
||||
|
||||
func (h *hub) onUnregister(id string) {
|
||||
func (h *connMgr) onUnregister(id string) {
|
||||
h.chanMutex.Lock()
|
||||
if conn, ok := h.connections[id]; ok {
|
||||
h.onlineCount--
|
||||
|
@ -160,7 +160,7 @@ func (h *hub) onUnregister(id string) {
|
|||
h.chanMutex.Unlock()
|
||||
}
|
||||
|
||||
func (h *hub) onBroadcast(packet hubBroadcast) {
|
||||
func (h *connMgr) onBroadcast(packet hubBroadcast) {
|
||||
h.chanMutex.RLock()
|
||||
defer h.chanMutex.RUnlock()
|
||||
for _, conn := range h.connections {
|
|
@ -7,18 +7,19 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
serverMultipleMark = "Minotaur Multiple Server"
|
||||
serverMark = "Minotaur Server"
|
||||
serverSystemDispatcher = "__system" // 系统消息分发器
|
||||
serverMultipleMark = "Minotaur Multiple Server"
|
||||
serverMark = "Minotaur Server"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAsyncPoolSize = 256
|
||||
DefaultWebsocketReadDeadline = 30 * time.Second
|
||||
DefaultPacketWarnSize = 1024 * 1024 * 1 // 1MB
|
||||
DefaultDispatcherBufferSize = 1024 * 16
|
||||
DefaultConnWriteBufferSize = 1024 * 1
|
||||
DefaultConnHubBufferSize = 1024 * 1
|
||||
DefaultAsyncPoolSize = 256
|
||||
DefaultWebsocketReadDeadline = 30 * time.Second
|
||||
DefaultPacketWarnSize = 1024 * 1024 * 1 // 1MB
|
||||
DefaultDispatcherBufferSize = 1024 * 16
|
||||
DefaultConnWriteBufferSize = 1024 * 1
|
||||
DefaultConnHubBufferSize = 1024 * 1
|
||||
DefaultLowMessageDuration = 100 * time.Millisecond
|
||||
DefaultAsyncLowMessageDuration = time.Second
|
||||
)
|
||||
|
||||
func DefaultWebsocketUpgrader() *websocket.Upgrader {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
108
server/event.go
108
server/event.go
|
@ -2,9 +2,10 @@ package server
|
|||
|
||||
import (
|
||||
"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/runtimes"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -42,51 +43,51 @@ type (
|
|||
func newEvent(srv *Server) *event {
|
||||
return &event{
|
||||
Server: srv,
|
||||
startBeforeEventHandlers: slice.NewPriority[StartBeforeEventHandler](),
|
||||
startFinishEventHandlers: slice.NewPriority[StartFinishEventHandler](),
|
||||
stopEventHandlers: slice.NewPriority[StopEventHandler](),
|
||||
connectionReceivePacketEventHandlers: slice.NewPriority[ConnectionReceivePacketEventHandler](),
|
||||
connectionOpenedEventHandlers: slice.NewPriority[ConnectionOpenedEventHandler](),
|
||||
connectionClosedEventHandlers: slice.NewPriority[ConnectionClosedEventHandler](),
|
||||
messageErrorEventHandlers: slice.NewPriority[MessageErrorEventHandler](),
|
||||
messageLowExecEventHandlers: slice.NewPriority[MessageLowExecEventHandler](),
|
||||
connectionOpenedAfterEventHandlers: slice.NewPriority[ConnectionOpenedAfterEventHandler](),
|
||||
connectionWritePacketBeforeHandlers: slice.NewPriority[ConnectionWritePacketBeforeEventHandler](),
|
||||
shuntChannelCreatedEventHandlers: slice.NewPriority[ShuntChannelCreatedEventHandler](),
|
||||
shuntChannelClosedEventHandlers: slice.NewPriority[ShuntChannelClosedEventHandler](),
|
||||
connectionPacketPreprocessEventHandlers: slice.NewPriority[ConnectionPacketPreprocessEventHandler](),
|
||||
messageExecBeforeEventHandlers: slice.NewPriority[MessageExecBeforeEventHandler](),
|
||||
messageReadyEventHandlers: slice.NewPriority[MessageReadyEventHandler](),
|
||||
deadlockDetectEventHandlers: slice.NewPriority[OnDeadlockDetectEventHandler](),
|
||||
startBeforeEventHandlers: listings.NewPrioritySlice[StartBeforeEventHandler](),
|
||||
startFinishEventHandlers: listings.NewPrioritySlice[StartFinishEventHandler](),
|
||||
stopEventHandlers: listings.NewPrioritySlice[StopEventHandler](),
|
||||
connectionReceivePacketEventHandlers: listings.NewPrioritySlice[ConnectionReceivePacketEventHandler](),
|
||||
connectionOpenedEventHandlers: listings.NewPrioritySlice[ConnectionOpenedEventHandler](),
|
||||
connectionClosedEventHandlers: listings.NewPrioritySlice[ConnectionClosedEventHandler](),
|
||||
messageErrorEventHandlers: listings.NewPrioritySlice[MessageErrorEventHandler](),
|
||||
messageLowExecEventHandlers: listings.NewPrioritySlice[MessageLowExecEventHandler](),
|
||||
connectionOpenedAfterEventHandlers: listings.NewPrioritySlice[ConnectionOpenedAfterEventHandler](),
|
||||
connectionWritePacketBeforeHandlers: listings.NewPrioritySlice[ConnectionWritePacketBeforeEventHandler](),
|
||||
shuntChannelCreatedEventHandlers: listings.NewPrioritySlice[ShuntChannelCreatedEventHandler](),
|
||||
shuntChannelClosedEventHandlers: listings.NewPrioritySlice[ShuntChannelClosedEventHandler](),
|
||||
connectionPacketPreprocessEventHandlers: listings.NewPrioritySlice[ConnectionPacketPreprocessEventHandler](),
|
||||
messageExecBeforeEventHandlers: listings.NewPrioritySlice[MessageExecBeforeEventHandler](),
|
||||
messageReadyEventHandlers: listings.NewPrioritySlice[MessageReadyEventHandler](),
|
||||
deadlockDetectEventHandlers: listings.NewPrioritySlice[OnDeadlockDetectEventHandler](),
|
||||
}
|
||||
}
|
||||
|
||||
type event struct {
|
||||
*Server
|
||||
startBeforeEventHandlers *slice.Priority[StartBeforeEventHandler]
|
||||
startFinishEventHandlers *slice.Priority[StartFinishEventHandler]
|
||||
stopEventHandlers *slice.Priority[StopEventHandler]
|
||||
connectionReceivePacketEventHandlers *slice.Priority[ConnectionReceivePacketEventHandler]
|
||||
connectionOpenedEventHandlers *slice.Priority[ConnectionOpenedEventHandler]
|
||||
connectionClosedEventHandlers *slice.Priority[ConnectionClosedEventHandler]
|
||||
messageErrorEventHandlers *slice.Priority[MessageErrorEventHandler]
|
||||
messageLowExecEventHandlers *slice.Priority[MessageLowExecEventHandler]
|
||||
connectionOpenedAfterEventHandlers *slice.Priority[ConnectionOpenedAfterEventHandler]
|
||||
connectionWritePacketBeforeHandlers *slice.Priority[ConnectionWritePacketBeforeEventHandler]
|
||||
shuntChannelCreatedEventHandlers *slice.Priority[ShuntChannelCreatedEventHandler]
|
||||
shuntChannelClosedEventHandlers *slice.Priority[ShuntChannelClosedEventHandler]
|
||||
connectionPacketPreprocessEventHandlers *slice.Priority[ConnectionPacketPreprocessEventHandler]
|
||||
messageExecBeforeEventHandlers *slice.Priority[MessageExecBeforeEventHandler]
|
||||
messageReadyEventHandlers *slice.Priority[MessageReadyEventHandler]
|
||||
deadlockDetectEventHandlers *slice.Priority[OnDeadlockDetectEventHandler]
|
||||
startBeforeEventHandlers *listings.PrioritySlice[StartBeforeEventHandler]
|
||||
startFinishEventHandlers *listings.PrioritySlice[StartFinishEventHandler]
|
||||
stopEventHandlers *listings.PrioritySlice[StopEventHandler]
|
||||
connectionReceivePacketEventHandlers *listings.PrioritySlice[ConnectionReceivePacketEventHandler]
|
||||
connectionOpenedEventHandlers *listings.PrioritySlice[ConnectionOpenedEventHandler]
|
||||
connectionClosedEventHandlers *listings.PrioritySlice[ConnectionClosedEventHandler]
|
||||
messageErrorEventHandlers *listings.PrioritySlice[MessageErrorEventHandler]
|
||||
messageLowExecEventHandlers *listings.PrioritySlice[MessageLowExecEventHandler]
|
||||
connectionOpenedAfterEventHandlers *listings.PrioritySlice[ConnectionOpenedAfterEventHandler]
|
||||
connectionWritePacketBeforeHandlers *listings.PrioritySlice[ConnectionWritePacketBeforeEventHandler]
|
||||
shuntChannelCreatedEventHandlers *listings.PrioritySlice[ShuntChannelCreatedEventHandler]
|
||||
shuntChannelClosedEventHandlers *listings.PrioritySlice[ShuntChannelClosedEventHandler]
|
||||
connectionPacketPreprocessEventHandlers *listings.PrioritySlice[ConnectionPacketPreprocessEventHandler]
|
||||
messageExecBeforeEventHandlers *listings.PrioritySlice[MessageExecBeforeEventHandler]
|
||||
messageReadyEventHandlers *listings.PrioritySlice[MessageReadyEventHandler]
|
||||
deadlockDetectEventHandlers *listings.PrioritySlice[OnDeadlockDetectEventHandler]
|
||||
|
||||
consoleCommandEventHandlers map[string]*slice.Priority[ConsoleCommandEventHandler]
|
||||
consoleCommandEventHandlers map[string]*listings.PrioritySlice[ConsoleCommandEventHandler]
|
||||
consoleCommandEventHandlerInitOnce sync.Once
|
||||
}
|
||||
|
||||
// RegStopEvent 服务器停止时将立即执行被注册的事件处理函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -108,7 +109,7 @@ func (slf *event) RegConsoleCommandEvent(command string, handler ConsoleCommandE
|
|||
}
|
||||
|
||||
slf.consoleCommandEventHandlerInitOnce.Do(func() {
|
||||
slf.consoleCommandEventHandlers = map[string]*slice.Priority[ConsoleCommandEventHandler]{}
|
||||
slf.consoleCommandEventHandlers = map[string]*listings.PrioritySlice[ConsoleCommandEventHandler]{}
|
||||
go func() {
|
||||
for {
|
||||
var input string
|
||||
|
@ -123,10 +124,10 @@ func (slf *event) RegConsoleCommandEvent(command string, handler ConsoleCommandE
|
|||
})
|
||||
list, exist := slf.consoleCommandEventHandlers[command]
|
||||
if !exist {
|
||||
list = slice.NewPriority[ConsoleCommandEventHandler]()
|
||||
list = listings.NewPrioritySlice[ConsoleCommandEventHandler]()
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -161,7 +162,7 @@ func (slf *event) OnConsoleCommandEvent(command string, paramsStr string) {
|
|||
|
||||
// RegStartBeforeEvent 在服务器初始化完成启动前立刻执行被注册的事件处理函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -181,7 +182,7 @@ func (slf *event) OnStartBeforeEvent() {
|
|||
// RegStartFinishEvent 在服务器启动完成时将立刻执行被注册的事件处理函数
|
||||
// - 需要注意该时刻服务器已经启动完成,但是还有可能未开始处理消息,客户端有可能无法连接,如果需要在消息处理器准备就绪后执行,请使用 RegMessageReadyEvent 函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -205,7 +206,7 @@ func (slf *event) RegConnectionClosedEvent(handler ConnectionClosedEventHandler,
|
|||
if slf.network == NetworkHttp {
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -216,6 +217,7 @@ func (slf *event) OnConnectionClosedEvent(conn *Conn, err any) {
|
|||
value(slf.Server, conn, err)
|
||||
return true
|
||||
})
|
||||
slf.Server.dispatcherMgr.UnBindProducer(conn.GetID())
|
||||
}, log.String("Event", "OnConnectionClosedEvent"))
|
||||
}
|
||||
|
||||
|
@ -225,7 +227,7 @@ func (slf *event) RegConnectionOpenedEvent(handler ConnectionOpenedEventHandler,
|
|||
if slf.network == NetworkHttp {
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -245,7 +247,7 @@ func (slf *event) RegConnectionReceivePacketEvent(handler ConnectionReceivePacke
|
|||
if slf.network == NetworkHttp {
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -261,7 +263,7 @@ func (slf *event) OnConnectionReceivePacketEvent(conn *Conn, packet []byte) {
|
|||
|
||||
// RegMessageErrorEvent 在处理消息发生错误时将立即执行被注册的事件处理函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -283,7 +285,7 @@ func (slf *event) OnMessageErrorEvent(message *Message, err error) {
|
|||
|
||||
// RegMessageLowExecEvent 在处理消息缓慢时将立即执行被注册的事件处理函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -304,7 +306,7 @@ func (slf *event) RegConnectionOpenedAfterEvent(handler ConnectionOpenedAfterEve
|
|||
if slf.network == NetworkHttp {
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -322,7 +324,7 @@ func (slf *event) RegConnectionWritePacketBeforeEvent(handler ConnectionWritePac
|
|||
if slf.network == NetworkHttp {
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -340,7 +342,7 @@ func (slf *event) OnConnectionWritePacketBeforeEvent(conn *Conn, packet []byte)
|
|||
|
||||
// RegShuntChannelCreatedEvent 在分流通道创建时将立刻执行被注册的事件处理函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -355,7 +357,7 @@ func (slf *event) OnShuntChannelCreatedEvent(name string) {
|
|||
|
||||
// RegShuntChannelCloseEvent 在分流通道关闭时将立刻执行被注册的事件处理函数
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -377,7 +379,7 @@ func (slf *event) OnShuntChannelClosedEvent(name string) {
|
|||
// - 数据包格式校验
|
||||
// - 数据包分包等情况处理
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -401,7 +403,7 @@ func (slf *event) OnConnectionPacketPreprocessEvent(conn *Conn, packet []byte, u
|
|||
//
|
||||
// 适用于限流等场景
|
||||
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()))
|
||||
}
|
||||
|
||||
|
@ -425,7 +427,7 @@ func (slf *event) OnMessageExecBeforeEvent(message *Message) bool {
|
|||
|
||||
// RegMessageReadyEvent 在服务器消息处理器准备就绪时立即执行被注册的事件处理函数
|
||||
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() {
|
||||
|
@ -446,7 +448,7 @@ func (slf *event) OnMessageReadyEvent() {
|
|||
|
||||
// RegDeadlockDetectEvent 在死锁检测触发时立即执行被注册的事件处理函数
|
||||
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) {
|
||||
|
|
|
@ -2,7 +2,8 @@ package gateway
|
|||
|
||||
import (
|
||||
"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 (
|
||||
|
@ -16,27 +17,27 @@ type (
|
|||
|
||||
func newEvents() *events {
|
||||
return &events{
|
||||
connectionOpenedEventHandles: slice.NewPriority[ConnectionOpenedEventHandle](),
|
||||
connectionClosedEventHandles: slice.NewPriority[ConnectionClosedEventHandle](),
|
||||
connectionReceivePacketEventHandles: slice.NewPriority[ConnectionReceivePacketEventHandle](),
|
||||
endpointConnectOpenedEventHandles: slice.NewPriority[EndpointConnectOpenedEventHandle](),
|
||||
endpointConnectClosedEventHandles: slice.NewPriority[EndpointConnectClosedEventHandle](),
|
||||
endpointConnectReceivePacketEventHandles: slice.NewPriority[EndpointConnectReceivePacketEventHandle](),
|
||||
connectionOpenedEventHandles: listings.NewPrioritySlice[ConnectionOpenedEventHandle](),
|
||||
connectionClosedEventHandles: listings.NewPrioritySlice[ConnectionClosedEventHandle](),
|
||||
connectionReceivePacketEventHandles: listings.NewPrioritySlice[ConnectionReceivePacketEventHandle](),
|
||||
endpointConnectOpenedEventHandles: listings.NewPrioritySlice[EndpointConnectOpenedEventHandle](),
|
||||
endpointConnectClosedEventHandles: listings.NewPrioritySlice[EndpointConnectClosedEventHandle](),
|
||||
endpointConnectReceivePacketEventHandles: listings.NewPrioritySlice[EndpointConnectReceivePacketEventHandle](),
|
||||
}
|
||||
}
|
||||
|
||||
type events struct {
|
||||
connectionOpenedEventHandles *slice.Priority[ConnectionOpenedEventHandle]
|
||||
connectionClosedEventHandles *slice.Priority[ConnectionClosedEventHandle]
|
||||
connectionReceivePacketEventHandles *slice.Priority[ConnectionReceivePacketEventHandle]
|
||||
endpointConnectOpenedEventHandles *slice.Priority[EndpointConnectOpenedEventHandle]
|
||||
endpointConnectClosedEventHandles *slice.Priority[EndpointConnectClosedEventHandle]
|
||||
endpointConnectReceivePacketEventHandles *slice.Priority[EndpointConnectReceivePacketEventHandle]
|
||||
connectionOpenedEventHandles *listings.PrioritySlice[ConnectionOpenedEventHandle]
|
||||
connectionClosedEventHandles *listings.PrioritySlice[ConnectionClosedEventHandle]
|
||||
connectionReceivePacketEventHandles *listings.PrioritySlice[ConnectionReceivePacketEventHandle]
|
||||
endpointConnectOpenedEventHandles *listings.PrioritySlice[EndpointConnectOpenedEventHandle]
|
||||
endpointConnectClosedEventHandles *listings.PrioritySlice[EndpointConnectClosedEventHandle]
|
||||
endpointConnectReceivePacketEventHandles *listings.PrioritySlice[EndpointConnectReceivePacketEventHandle]
|
||||
}
|
||||
|
||||
// RegConnectionOpenedEventHandle 注册客户端连接打开事件处理函数
|
||||
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) {
|
||||
|
@ -48,7 +49,7 @@ func (slf *events) OnConnectionOpenedEvent(gateway *Gateway, conn *server.Conn)
|
|||
|
||||
// RegConnectionClosedEventHandle 注册客户端连接关闭事件处理函数
|
||||
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) {
|
||||
|
@ -60,7 +61,7 @@ func (slf *events) OnConnectionClosedEvent(gateway *Gateway, conn *server.Conn)
|
|||
|
||||
// RegConnectionReceivePacketEventHandle 注册客户端连接接收数据包事件处理函数
|
||||
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) {
|
||||
|
@ -72,7 +73,7 @@ func (slf *events) OnConnectionReceivePacketEvent(gateway *Gateway, conn *server
|
|||
|
||||
// RegEndpointConnectOpenedEventHandle 注册端点连接打开事件处理函数
|
||||
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) {
|
||||
|
@ -84,7 +85,7 @@ func (slf *events) OnEndpointConnectOpenedEvent(gateway *Gateway, endpoint *Endp
|
|||
|
||||
// RegEndpointConnectClosedEventHandle 注册端点连接关闭事件处理函数
|
||||
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) {
|
||||
|
@ -96,7 +97,7 @@ func (slf *events) OnEndpointConnectClosedEvent(gateway *Gateway, endpoint *Endp
|
|||
|
||||
// RegEndpointConnectReceivePacketEventHandle 注册端点连接接收数据包事件处理函数
|
||||
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) {
|
||||
|
|
|
@ -7,19 +7,19 @@ import (
|
|||
// NewHttpContext 基于 gin.Context 创建一个新的 HttpContext
|
||||
func NewHttpContext(ctx *gin.Context) *HttpContext {
|
||||
hc := &HttpContext{
|
||||
ctx: ctx,
|
||||
Context: ctx,
|
||||
}
|
||||
return hc
|
||||
}
|
||||
|
||||
// HttpContext 基于 gin.Context 的 http 请求上下文
|
||||
type HttpContext struct {
|
||||
ctx *gin.Context
|
||||
*gin.Context
|
||||
}
|
||||
|
||||
// Gin 获取 gin.Context
|
||||
func (slf *HttpContext) Gin() *gin.Context {
|
||||
return slf.ctx
|
||||
return slf.Context
|
||||
}
|
||||
|
||||
// ReadTo 读取请求数据到指定结构体,如果失败则返回错误
|
||||
|
|
|
@ -27,7 +27,7 @@ func (slf *HttpRouter[Context]) handlesConvert(handlers []HandlerFunc[Context])
|
|||
hc := slf.packer(ctx)
|
||||
var now = time.Now()
|
||||
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
|
||||
|
@ -146,3 +146,9 @@ func (slf *HttpRouter[Context]) Group(relativePath string, handlers ...HandlerFu
|
|||
packer: slf.packer,
|
||||
}
|
||||
}
|
||||
|
||||
// Use 将中间件附加到路由组。
|
||||
func (slf *HttpRouter[Context]) Use(middleware ...HandlerFunc[Context]) *HttpRouter[Context] {
|
||||
slf.group.Use(slf.handlesConvert(middleware)...)
|
||||
return slf
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package dispatcher
|
||||
|
||||
type Message[P comparable] interface {
|
||||
// GetProducer 获取消息生产者
|
||||
GetProducer() P
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package dispatcher
|
||||
|
||||
type Producer interface {
|
||||
comparable
|
||||
}
|
|
@ -96,7 +96,7 @@ func (slf *Lockstep[ClientID, Command]) GetClientCount() int {
|
|||
func (slf *Lockstep[ClientID, Command]) DropCache(handler func(frame int64) bool) {
|
||||
slf.frameCacheLock.Lock()
|
||||
defer slf.frameCacheLock.Unlock()
|
||||
for frame, _ := range slf.frameCache {
|
||||
for frame := range slf.frameCache {
|
||||
if handler(frame) {
|
||||
delete(slf.frameCache, frame)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package server
|
||||
|
||||
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/super"
|
||||
)
|
||||
|
@ -70,20 +71,32 @@ type (
|
|||
|
||||
// HasMessageType 检查是否存在指定的消息类型
|
||||
func HasMessageType(mt MessageType) bool {
|
||||
return hash.Exist(messageNames, mt)
|
||||
return collection.KeyInMap(messageNames, mt)
|
||||
}
|
||||
|
||||
// Message 服务器消息
|
||||
type Message struct {
|
||||
dis *dispatcher.Dispatcher[string, *Message] // 指定消息发送到特定的分发器
|
||||
conn *Conn
|
||||
err error
|
||||
ordinaryHandler func()
|
||||
exceptionHandler func() error
|
||||
errHandler func(err error)
|
||||
marks []log.Field
|
||||
packet []byte
|
||||
err error
|
||||
producer string
|
||||
name string
|
||||
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 重置消息结构体
|
||||
|
@ -97,6 +110,8 @@ func (slf *Message) reset() {
|
|||
slf.name = ""
|
||||
slf.t = 0
|
||||
slf.marks = nil
|
||||
slf.producer = ""
|
||||
slf.dis = nil
|
||||
}
|
||||
|
||||
// MessageType 返回消息类型
|
||||
|
@ -126,78 +141,91 @@ func (slf MessageType) String() string {
|
|||
|
||||
// castToPacketMessage 将消息转换为数据包消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToTickerMessage 将消息转换为定时器消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToShuntTickerMessage 将消息转换为分发器定时器消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToAsyncMessage 将消息转换为异步消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToAsyncCallbackMessage 将消息转换为异步回调消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToShuntAsyncMessage 将消息转换为分流异步消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToShuntAsyncCallbackMessage 将消息转换为分流异步回调消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToUniqueAsyncMessage 将消息转换为唯一异步消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToUniqueAsyncCallbackMessage 将消息转换为唯一异步回调消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToUniqueShuntAsyncMessage 将消息转换为唯一分流异步消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToUniqueShuntAsyncCallbackMessage 将消息转换为唯一分流异步回调消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToSystemMessage 将消息转换为系统消息
|
||||
func (slf *Message) castToSystemMessage(caller func(), mark ...log.Field) *Message {
|
||||
slf.producer = "sys"
|
||||
slf.t, slf.ordinaryHandler, slf.marks = MessageTypeSystem, caller, mark
|
||||
return slf
|
||||
}
|
||||
|
||||
// castToShuntMessage 将消息转换为分流消息
|
||||
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
|
||||
return slf
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@ import (
|
|||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"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/slice"
|
||||
"github.com/kercylan98/minotaur/utils/super"
|
||||
"github.com/panjf2000/gnet"
|
||||
"github.com/xtaci/kcp-go/v5"
|
||||
|
@ -42,6 +41,17 @@ var (
|
|||
networks = []Network{
|
||||
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() {
|
||||
|
@ -53,12 +63,12 @@ func init() {
|
|||
|
||||
// GetNetworks 获取所有支持的网络模式
|
||||
func GetNetworks() []Network {
|
||||
return slice.Copy(networks)
|
||||
return collection.CloneSlice(networks)
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
@ -306,3 +316,8 @@ func (n Network) websocketMode(state chan<- error, srv *Server) {
|
|||
}
|
||||
}((&listener{srv: srv, Listener: l, state: state}).init())
|
||||
}
|
||||
|
||||
// IsSocket 返回当前服务器的网络模式是否为 Socket 模式
|
||||
func (n Network) IsSocket() bool {
|
||||
return collection.KeyInMap(socketNetworks, n)
|
||||
}
|
||||
|
|
|
@ -32,27 +32,46 @@ type option struct {
|
|||
}
|
||||
|
||||
type runtime struct {
|
||||
deadlockDetect time.Duration // 是否开启死锁检测
|
||||
supportMessageTypes map[int]bool // websocket 模式下支持的消息类型
|
||||
certFile, keyFile string // TLS文件
|
||||
tickerPool *timer.Pool // 定时器池
|
||||
ticker *timer.Ticker // 定时器
|
||||
tickerAutonomy bool // 定时器是否独立运行
|
||||
connTickerSize int // 连接定时器大小
|
||||
websocketReadDeadline time.Duration // websocket 连接超时时间
|
||||
websocketCompression int // websocket 压缩等级
|
||||
websocketWriteCompression bool // websocket 写入压缩
|
||||
limitLife time.Duration // 限制最大生命周期
|
||||
packetWarnSize int // 数据包大小警告
|
||||
messageStatisticsDuration time.Duration // 消息统计时长
|
||||
messageStatisticsLimit int // 消息统计数量
|
||||
messageStatistics []*atomic.Int64 // 消息统计数量
|
||||
messageStatisticsLock *sync.RWMutex // 消息统计锁
|
||||
dispatcherBufferSize int // 消息分发器缓冲区大小
|
||||
connWriteBufferSize int // 连接写入缓冲区大小
|
||||
disableAutomaticReleaseShunt bool // 是否禁用自动释放分流渠道
|
||||
websocketUpgrader *websocket.Upgrader // websocket 升级器
|
||||
websocketConnInitializer func(writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error // websocket 连接初始化
|
||||
deadlockDetect time.Duration // 是否开启死锁检测
|
||||
supportMessageTypes map[int]bool // websocket 模式下支持的消息类型
|
||||
certFile, keyFile string // TLS文件
|
||||
tickerPool *timer.Pool // 定时器池
|
||||
ticker *timer.Ticker // 定时器
|
||||
tickerAutonomy bool // 定时器是否独立运行
|
||||
connTickerSize int // 连接定时器大小
|
||||
websocketReadDeadline time.Duration // websocket 连接超时时间
|
||||
websocketCompression int // websocket 压缩等级
|
||||
websocketWriteCompression bool // websocket 写入压缩
|
||||
limitLife time.Duration // 限制最大生命周期
|
||||
packetWarnSize int // 数据包大小警告
|
||||
messageStatisticsDuration time.Duration // 消息统计时长
|
||||
messageStatisticsLimit int // 消息统计数量
|
||||
messageStatistics []*atomic.Int64 // 消息统计数量
|
||||
messageStatisticsLock *sync.RWMutex // 消息统计锁
|
||||
connWriteBufferSize int // 连接写入缓冲区大小
|
||||
websocketUpgrader *websocket.Upgrader // websocket 升级器
|
||||
websocketConnInitializer func(writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error // websocket 连接初始化
|
||||
dispatcherBufferSize int // 消息分发器缓冲区大小
|
||||
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 返回错误时,服务器将不会处理该连接的后续逻辑
|
||||
|
@ -78,14 +97,6 @@ func WithWebsocketUpgrade(upgrader *websocket.Upgrader) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithDisableAutomaticReleaseShunt 通过禁用自动释放分流渠道的方式创建服务器
|
||||
// - 默认不开启,当禁用自动释放分流渠道时,服务器将不会在连接断开时自动释放分流渠道,需要手动调用 ReleaseShunt 方法释放
|
||||
func WithDisableAutomaticReleaseShunt() Option {
|
||||
return func(srv *Server) {
|
||||
srv.runtime.disableAutomaticReleaseShunt = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnWriteBufferSize 通过连接写入缓冲区大小的方式创建服务器
|
||||
// - 默认值为 DefaultConnWriteBufferSize
|
||||
// - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存
|
||||
|
|
328
server/server.go
328
server/server.go
|
@ -5,11 +5,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kercylan98/minotaur/server/internal/dispatcher"
|
||||
"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/network"
|
||||
"github.com/kercylan98/minotaur/utils/sher"
|
||||
"github.com/kercylan98/minotaur/utils/str"
|
||||
"github.com/kercylan98/minotaur/utils/super"
|
||||
"github.com/kercylan98/minotaur/utils/timer"
|
||||
|
@ -21,7 +22,6 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -32,18 +32,17 @@ func New(network Network, options ...Option) *Server {
|
|||
network.check()
|
||||
server := &Server{
|
||||
runtime: &runtime{
|
||||
packetWarnSize: DefaultPacketWarnSize,
|
||||
dispatcherBufferSize: DefaultDispatcherBufferSize,
|
||||
connWriteBufferSize: DefaultConnWriteBufferSize,
|
||||
packetWarnSize: DefaultPacketWarnSize,
|
||||
connWriteBufferSize: DefaultConnWriteBufferSize,
|
||||
dispatcherBufferSize: DefaultDispatcherBufferSize,
|
||||
lowMessageDuration: DefaultLowMessageDuration,
|
||||
asyncLowMessageDuration: DefaultAsyncLowMessageDuration,
|
||||
},
|
||||
hub: &hub{},
|
||||
option: &option{},
|
||||
network: network,
|
||||
closeChannel: make(chan struct{}, 1),
|
||||
systemSignal: make(chan os.Signal, 1),
|
||||
dispatchers: make(map[string]*dispatcher),
|
||||
dispatcherMember: map[string]map[string]*Conn{},
|
||||
currDispatcher: map[string]*dispatcher{},
|
||||
connMgr: &connMgr{},
|
||||
option: &option{},
|
||||
network: network,
|
||||
closeChannel: make(chan struct{}, 1),
|
||||
systemSignal: make(chan os.Signal, 1),
|
||||
}
|
||||
server.ctx, server.cancel = context.WithCancel(context.Background())
|
||||
server.event = newEvent(server)
|
||||
|
@ -68,32 +67,29 @@ func New(network Network, options ...Option) *Server {
|
|||
|
||||
// Server 网络服务器
|
||||
type Server struct {
|
||||
*event // 事件
|
||||
*runtime // 运行时
|
||||
*option // 可选项
|
||||
*hub // 连接集合
|
||||
ginServer *gin.Engine // HTTP模式下的路由器
|
||||
httpServer *http.Server // HTTP模式下的服务器
|
||||
grpcServer *grpc.Server // GRPC模式下的服务器
|
||||
gServer *gNet // TCP或UDP模式下的服务器
|
||||
multiple *MultipleServer // 多服务器模式下的服务器
|
||||
ants *ants.Pool // 协程池
|
||||
messagePool *concurrent.Pool[*Message] // 消息池
|
||||
ctx context.Context // 上下文
|
||||
cancel context.CancelFunc // 停止上下文
|
||||
systemDispatcher *dispatcher // 系统消息分发器
|
||||
systemSignal chan os.Signal // 系统信号
|
||||
closeChannel chan struct{} // 关闭信号
|
||||
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
|
||||
dispatchers map[string]*dispatcher // 消息分发器集合
|
||||
dispatcherMember map[string]map[string]*Conn // 消息分发器包含的连接
|
||||
currDispatcher map[string]*dispatcher // 当前连接所处消息分发器
|
||||
dispatcherLock sync.RWMutex // 消息分发器锁
|
||||
messageCounter atomic.Int64 // 消息计数器
|
||||
addr string // 侦听地址
|
||||
network Network // 网络类型
|
||||
closed uint32 // 服务器是否已关闭
|
||||
services []func() // 服务
|
||||
*event // 事件
|
||||
*runtime // 运行时
|
||||
*option // 可选项
|
||||
*connMgr // 连接集合
|
||||
dispatcherMgr *dispatcher.Manager[string, *Message] // 消息分发器管理器
|
||||
ginServer *gin.Engine // HTTP模式下的路由器
|
||||
httpServer *http.Server // HTTP模式下的服务器
|
||||
grpcServer *grpc.Server // GRPC模式下的服务器
|
||||
gServer *gNet // TCP或UDP模式下的服务器
|
||||
multiple *MultipleServer // 多服务器模式下的服务器
|
||||
ants *ants.Pool // 协程池
|
||||
messagePool *hub.ObjectPool[*Message] // 消息池
|
||||
ctx context.Context // 上下文
|
||||
cancel context.CancelFunc // 停止上下文
|
||||
systemSignal chan os.Signal // 系统信号
|
||||
closeChannel chan struct{} // 关闭信号
|
||||
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
|
||||
|
||||
messageCounter atomic.Int64 // 消息计数器
|
||||
addr string // 侦听地址
|
||||
network Network // 网络类型
|
||||
closed uint32 // 服务器是否已关闭
|
||||
services []func() // 服务
|
||||
}
|
||||
|
||||
// preCheckAndAdaptation 预检查及适配
|
||||
|
@ -106,7 +102,7 @@ func (srv *Server) preCheckAndAdaptation(addr string) (startState <-chan error,
|
|||
kcp.SystemTimedSched.Close()
|
||||
}
|
||||
|
||||
srv.hub.run(srv.ctx)
|
||||
srv.connMgr.run(srv.ctx)
|
||||
return srv.network.adaptation(srv), nil
|
||||
}
|
||||
|
||||
|
@ -155,9 +151,7 @@ func (srv *Server) Run(addr string) (err error) {
|
|||
|
||||
// IsSocket 是否是 Socket 模式
|
||||
func (srv *Server) IsSocket() bool {
|
||||
return srv.network == NetworkTcp || srv.network == NetworkTcp4 || srv.network == NetworkTcp6 ||
|
||||
srv.network == NetworkUdp || srv.network == NetworkUdp4 || srv.network == NetworkUdp6 ||
|
||||
srv.network == NetworkUnix || srv.network == NetworkKcp || srv.network == NetworkWebsocket
|
||||
return srv.network.IsSocket()
|
||||
}
|
||||
|
||||
// RunNone 是 Run("") 的简写,仅适用于运行 NetworkNone 服务器
|
||||
|
@ -197,11 +191,41 @@ func (srv *Server) shutdown(err error) {
|
|||
log.Error("Server", log.String("state", "shutdown"), log.Err(err))
|
||||
}
|
||||
|
||||
var infoCount int
|
||||
for srv.messageCounter.Load() > 0 {
|
||||
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()))
|
||||
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("message", srv.messageCounter.Load()))
|
||||
}
|
||||
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 {
|
||||
srv.OnStopEvent()
|
||||
}
|
||||
|
@ -222,13 +246,6 @@ func (srv *Server) shutdown(err error) {
|
|||
srv.ants.Release()
|
||||
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 {
|
||||
srv.grpcServer.GracefulStop()
|
||||
}
|
||||
|
@ -300,109 +317,25 @@ func (srv *Server) GetMessageCount() int64 {
|
|||
}
|
||||
|
||||
// UseShunt 切换连接所使用的消息分流渠道,当分流渠道 name 不存在时将会创建一个新的分流渠道,否则将会加入已存在的分流渠道
|
||||
// - 默认情况下,所有连接都使用系统通道进行消息分发,当指定消息分流渠道时,将会使用指定的消息分流渠道进行消息分发
|
||||
// - 在使用 WithDisableAutomaticReleaseShunt 创建服务器后,必须始终在连接不再使用后主动通过 ReleaseShunt 释放消息分流渠道,否则将造成内存泄漏
|
||||
// - 默认情况下,所有连接都使用系统通道进行消息分发,当指定消息分流渠道且为分流消息类型时,将会使用指定的消息分流渠道进行消息分发
|
||||
// - 分流渠道会在连接断开时标记为驱逐状态,当分流渠道中的所有消息处理完毕且没有新连接使用时,将会被清除
|
||||
func (srv *Server) UseShunt(conn *Conn, name string) {
|
||||
srv.dispatcherLock.Lock()
|
||||
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
|
||||
srv.dispatcherMgr.BindProducer(conn.GetID(), name)
|
||||
}
|
||||
|
||||
// HasShunt 检查特定消息分流渠道是否存在
|
||||
func (srv *Server) HasShunt(name string) bool {
|
||||
srv.dispatcherLock.RLock()
|
||||
defer srv.dispatcherLock.RUnlock()
|
||||
_, exist := srv.dispatchers[name]
|
||||
return exist
|
||||
return srv.dispatcherMgr.HasDispatcher(name)
|
||||
}
|
||||
|
||||
// GetConnCurrShunt 获取连接当前所使用的消息分流渠道
|
||||
func (srv *Server) GetConnCurrShunt(conn *Conn) string {
|
||||
srv.dispatcherLock.RLock()
|
||||
defer srv.dispatcherLock.RUnlock()
|
||||
d, exist := srv.currDispatcher[conn.GetID()]
|
||||
if exist {
|
||||
return d.name
|
||||
}
|
||||
return serverSystemDispatcher
|
||||
return srv.dispatcherMgr.GetDispatcher(conn.GetID()).Name()
|
||||
}
|
||||
|
||||
// GetShuntNum 获取消息分流渠道数量
|
||||
func (srv *Server) GetShuntNum() int {
|
||||
srv.dispatcherLock.RLock()
|
||||
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)
|
||||
}
|
||||
return srv.dispatcherMgr.GetDispatcherNum()
|
||||
}
|
||||
|
||||
// pushMessage 向服务器中写入特定类型的消息,需严格遵守消息属性要求
|
||||
|
@ -411,28 +344,40 @@ func (srv *Server) pushMessage(message *Message) {
|
|||
srv.messagePool.Release(message)
|
||||
return
|
||||
}
|
||||
var dispatcher *dispatcher
|
||||
switch message.t {
|
||||
case MessageTypePacket,
|
||||
MessageTypeShuntTicker, MessageTypeShuntAsync, MessageTypeShuntAsyncCallback,
|
||||
MessageTypeUniqueShuntAsync, MessageTypeUniqueShuntAsyncCallback,
|
||||
MessageTypeShunt:
|
||||
dispatcher = srv.getConnDispatcher(message.conn)
|
||||
case MessageTypeSystem, MessageTypeAsync, MessageTypeUniqueAsync, MessageTypeAsyncCallback, MessageTypeUniqueAsyncCallback, MessageTypeTicker:
|
||||
dispatcher = srv.systemDispatcher
|
||||
var d = message.dis
|
||||
if d == nil {
|
||||
switch message.t {
|
||||
case MessageTypePacket,
|
||||
MessageTypeShuntTicker, MessageTypeShuntAsync, MessageTypeShuntAsyncCallback,
|
||||
MessageTypeUniqueShuntAsync, MessageTypeUniqueShuntAsyncCallback,
|
||||
MessageTypeShunt:
|
||||
d = srv.dispatcherMgr.GetDispatcher(message.conn.GetID())
|
||||
case MessageTypeSystem, MessageTypeAsync, MessageTypeUniqueAsync, MessageTypeAsyncCallback, MessageTypeUniqueAsyncCallback, MessageTypeTicker:
|
||||
d = srv.dispatcherMgr.GetSystemDispatcher()
|
||||
}
|
||||
}
|
||||
if dispatcher == nil {
|
||||
if d == nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
switch message.t {
|
||||
case MessageTypeShuntAsync, MessageTypeUniqueShuntAsync:
|
||||
d.IncrCount(message.conn.GetID(), 1)
|
||||
}
|
||||
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)
|
||||
if cost > expect {
|
||||
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, message.marks...)
|
||||
//fields = append(fields, log.Stack("stack"))
|
||||
log.Warn("ServerLowMessage", sher.SliceCastToAny(fields)...)
|
||||
log.Warn("ServerLowMessage", collection.ConvertSliceToAny(fields)...)
|
||||
srv.OnMessageLowExecEvent(message, cost)
|
||||
}
|
||||
}
|
||||
|
||||
// dispatchMessage 消息分发
|
||||
func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
|
||||
func (srv *Server) dispatchMessage(dispatcherIns *dispatcher.Dispatcher[string, *Message], msg *Message) {
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
@ -478,7 +423,7 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
|
|||
|
||||
present := time.Now()
|
||||
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)
|
||||
if err := super.RecoverTransform(recover()); err != nil {
|
||||
stack := string(debug.Stack())
|
||||
|
@ -486,11 +431,15 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
|
|||
fmt.Println(stack)
|
||||
srv.OnMessageErrorEvent(msg, err)
|
||||
}
|
||||
if msg.t == MessageTypeUniqueAsyncCallback || msg.t == MessageTypeUniqueShuntAsyncCallback {
|
||||
dispatcherIns.antiUnique(msg.name)
|
||||
switch msg.t {
|
||||
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)
|
||||
|
||||
if atomic.CompareAndSwapUint32(&srv.closed, 0, 0) {
|
||||
|
@ -514,10 +463,14 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
|
|||
msg.ordinaryHandler()
|
||||
case MessageTypeAsync, MessageTypeShuntAsync, MessageTypeUniqueAsync, MessageTypeUniqueShuntAsync:
|
||||
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 msg.t == MessageTypeUniqueAsync || msg.t == MessageTypeUniqueShuntAsync {
|
||||
dispatcherIns.antiUnique(msg.name)
|
||||
dispatcherIns.AntiUnique(msg.name)
|
||||
}
|
||||
stack := string(debug.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)
|
||||
}
|
||||
super.Handle(cancel)
|
||||
srv.low(msg, present, time.Second)
|
||||
srv.low(msg, present, srv.asyncLowMessageDuration, true)
|
||||
srv.messageCounter.Add(-1)
|
||||
|
||||
if atomic.CompareAndSwapUint32(&srv.closed, 0, 0) {
|
||||
|
@ -534,25 +487,27 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
|
|||
}(cancel, srv, dispatcherIns, msg, present)
|
||||
var err error
|
||||
if msg.exceptionHandler != nil {
|
||||
dispatcherIns.IncrCount(msg.producer, 1)
|
||||
err = msg.exceptionHandler()
|
||||
}
|
||||
if msg.errHandler != nil {
|
||||
if msg.conn == nil {
|
||||
if msg.t == MessageTypeUniqueAsync {
|
||||
srv.PushUniqueAsyncCallbackMessage(msg.name, err, msg.errHandler)
|
||||
srv.pushUniqueAsyncCallbackMessage(dispatcherIns, msg.name, err, msg.errHandler)
|
||||
return
|
||||
}
|
||||
srv.PushAsyncCallbackMessage(err, msg.errHandler)
|
||||
srv.pushAsyncCallbackMessage(dispatcherIns, err, msg.errHandler)
|
||||
return
|
||||
}
|
||||
if msg.t == MessageTypeUniqueShuntAsync {
|
||||
srv.PushUniqueShuntAsyncCallbackMessage(msg.conn, msg.name, err, msg.errHandler)
|
||||
srv.pushUniqueShuntAsyncCallbackMessage(dispatcherIns, msg.conn, msg.name, err, msg.errHandler)
|
||||
return
|
||||
}
|
||||
srv.PushShuntAsyncCallbackMessage(msg.conn, err, msg.errHandler)
|
||||
srv.pushShuntAsyncCallbackMessage(dispatcherIns, msg.conn, err, msg.errHandler)
|
||||
return
|
||||
}
|
||||
dispatcherIns.antiUnique(msg.name)
|
||||
dispatcherIns.AntiUnique(msg.name)
|
||||
dispatcherIns.IncrCount(msg.producer, -1)
|
||||
if err != nil {
|
||||
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...))
|
||||
}
|
||||
|
||||
// PushAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息
|
||||
// pushAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息
|
||||
// - 异步消息回调将会通过一个接收 error 的函数进行处理,该函数将在系统分发器中执行
|
||||
// - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现
|
||||
func (srv *Server) PushAsyncCallbackMessage(err error, callback func(err error), mark ...log.Field) {
|
||||
srv.pushMessage(srv.messagePool.Get().castToAsyncCallbackMessage(err, callback, mark...))
|
||||
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...).bindDispatcher(dis))
|
||||
}
|
||||
|
||||
// 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...))
|
||||
}
|
||||
|
||||
// PushShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
|
||||
// - 需要注意的是,当未指定 UseShunt 时,将会通过 PushAsyncCallbackMessage 进行转发
|
||||
func (srv *Server) PushShuntAsyncCallbackMessage(conn *Conn, err error, callback func(err error), mark ...log.Field) {
|
||||
srv.pushMessage(srv.messagePool.Get().castToShuntAsyncCallbackMessage(conn, err, callback, mark...))
|
||||
// pushShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
|
||||
// - 需要注意的是,当未指定 UseShunt 时,将会通过 pushAsyncCallbackMessage 进行转发
|
||||
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...).bindDispatcher(dis))
|
||||
}
|
||||
|
||||
// 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...))
|
||||
}
|
||||
|
||||
// PushUniqueAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
|
||||
func (srv *Server) PushUniqueAsyncCallbackMessage(unique string, err error, callback func(err error), mark ...log.Field) {
|
||||
srv.pushMessage(srv.messagePool.Get().castToUniqueAsyncCallbackMessage(unique, err, callback, mark...))
|
||||
// pushUniqueAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
|
||||
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...).bindDispatcher(dis))
|
||||
}
|
||||
|
||||
// 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...))
|
||||
}
|
||||
|
||||
// PushUniqueShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
|
||||
// pushUniqueShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
|
||||
// - 需要注意的是,当未指定 UseShunt 时,将会通过系统分流渠道进行转发
|
||||
func (srv *Server) PushUniqueShuntAsyncCallbackMessage(conn *Conn, unique string, err error, callback func(err error), mark ...log.Field) {
|
||||
srv.pushMessage(srv.messagePool.Get().castToUniqueShuntAsyncCallbackMessage(conn, unique, err, callback, mark...))
|
||||
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...).bindDispatcher(dis))
|
||||
}
|
||||
|
||||
// PushShuntMessage 向特定分发器中推送 MessageTypeShunt 消息,消息执行与 MessageTypeSystem 一致,不同的是将会在特定分发器中执行
|
||||
|
@ -762,7 +717,7 @@ func onServicesInit(srv *Server) {
|
|||
|
||||
// onMessageSystemInit 消息系统初始化
|
||||
func onMessageSystemInit(srv *Server) {
|
||||
srv.messagePool = concurrent.NewPool[Message](
|
||||
srv.messagePool = hub.NewObjectPool[Message](
|
||||
func() *Message {
|
||||
return &Message{}
|
||||
},
|
||||
|
@ -771,7 +726,8 @@ func onMessageSystemInit(srv *Server) {
|
|||
},
|
||||
)
|
||||
srv.startMessageStatistics()
|
||||
srv.systemDispatcher = generateDispatcher(srv.dispatcherBufferSize, serverSystemDispatcher, srv.dispatchMessage)
|
||||
go srv.systemDispatcher.start()
|
||||
srv.dispatcherMgr = dispatcher.NewManager[string, *Message](srv.dispatcherBufferSize, srv.dispatchMessage).
|
||||
SetDispatcherCreatedHandler(srv.OnShuntChannelCreatedEvent).
|
||||
SetDispatcherClosedHandler(srv.OnShuntChannelClosedEvent)
|
||||
srv.OnMessageReadyEvent()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestNew(t *testing.T) {
|
|||
fmt.Println("启动完成")
|
||||
})
|
||||
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) {
|
||||
|
@ -38,7 +38,7 @@ func TestNew2(t *testing.T) {
|
|||
fmt.Println("启动完成")
|
||||
})
|
||||
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) {
|
||||
|
|
|
@ -14,7 +14,8 @@ type Service interface {
|
|||
|
||||
// BindService 绑定服务到特定 Server,被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法
|
||||
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() {
|
||||
name := reflect.TypeOf(service).String()
|
||||
defer func(name string) {
|
||||
|
|
|
@ -29,9 +29,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/server/writeloop"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package writeloop
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
"github.com/kercylan98/minotaur/utils/log"
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,7 @@ import (
|
|||
// - errorHandler 错误处理函数
|
||||
//
|
||||
// 传入 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]{
|
||||
c: make(chan Message, channelSize),
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ type Channel[T any] struct {
|
|||
c chan T
|
||||
}
|
||||
|
||||
// Put 将数据放入写循环,message 应该来源于 concurrent.Pool
|
||||
// Put 将数据放入写循环,message 应该来源于 hub.ObjectPool
|
||||
func (slf *Channel[T]) Put(message T) {
|
||||
slf.c <- message
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package writeloop
|
|||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/buffer"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
"github.com/kercylan98/minotaur/utils/log"
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,7 @@ import (
|
|||
// - errorHandler 错误处理函数
|
||||
//
|
||||
// 传入 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]{
|
||||
buf: buffer.NewUnbounded[Message](),
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ type Unbounded[Message any] struct {
|
|||
buf *buffer.Unbounded[Message]
|
||||
}
|
||||
|
||||
// Put 将数据放入写循环,message 应该来源于 concurrent.Pool
|
||||
// Put 将数据放入写循环,message 应该来源于 hub.ObjectPool
|
||||
func (slf *Unbounded[Message]) Put(message Message) {
|
||||
slf.buf.Put(message)
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ package writeloop_test
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/server/writeloop"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func ExampleNewUnbounded() {
|
||||
pool := concurrent.NewPool[Message](func() *Message {
|
||||
pool := hub.NewObjectPool[Message](func() *Message {
|
||||
return &Message{}
|
||||
}, func(data *Message) {
|
||||
data.ID = 0
|
||||
|
|
|
@ -2,7 +2,7 @@ package writeloop_test
|
|||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/server/writeloop"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/hub"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
@ -11,7 +11,7 @@ type Message struct {
|
|||
ID int
|
||||
}
|
||||
|
||||
var wp = concurrent.NewPool(func() *Message {
|
||||
var wp = hub.NewObjectPool(func() *Message {
|
||||
return &Message{}
|
||||
}, func(data *Message) {
|
||||
data.ID = 0
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package aoi
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/geometry"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"math"
|
||||
"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 {
|
||||
slf.rw.RLock()
|
||||
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) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package arrangement
|
||||
|
||||
import "github.com/kercylan98/minotaur/utils/hash"
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
// Area 编排区域
|
||||
type Area[ID comparable, AreaInfo any] struct {
|
||||
|
@ -43,7 +45,7 @@ func (slf *Area[ID, AreaInfo]) IsAllow(item Item[ID]) (constraintErr error, conf
|
|||
|
||||
// IsConflict 检测一个成员是否会造成冲突
|
||||
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
|
||||
}
|
||||
for _, conflict := range slf.conflicts {
|
||||
|
@ -56,7 +58,7 @@ func (slf *Area[ID, AreaInfo]) IsConflict(item Item[ID]) bool {
|
|||
|
||||
// GetConflictItems 获取与一个成员产生冲突的所有其他成员
|
||||
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
|
||||
}
|
||||
var conflictItems map[ID]Item[ID]
|
||||
|
@ -79,7 +81,7 @@ func (slf *Area[ID, AreaInfo]) GetScore(extra ...Item[ID]) float64 {
|
|||
if slf.evaluate == nil {
|
||||
return 0
|
||||
}
|
||||
var items = hash.Copy(slf.items)
|
||||
var items = collection.CloneMap(slf.items)
|
||||
for _, item := range extra {
|
||||
items[item.GetID()] = item
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package arrangement
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
@ -59,8 +59,8 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange() (areas []*Area[ID, AreaInfo], no
|
|||
return slf.areas, slf.items
|
||||
}
|
||||
|
||||
var items = hash.Copy(slf.items)
|
||||
var fixed = hash.Copy(slf.fixed)
|
||||
var items = collection.CloneMap(slf.items)
|
||||
var fixed = collection.CloneMap(slf.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]{
|
||||
a: slf,
|
||||
pending: hash.ToSlice(items),
|
||||
pending: collection.ConvertMapValuesToSlice(items),
|
||||
falls: map[ID]struct{}{},
|
||||
}
|
||||
sort.Slice(editor.pending, func(i, j int) bool {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package arrangement
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
@ -33,7 +32,7 @@ func (slf *Editor[ID, AreaInfo]) RemoveAreaItem(area *Area[ID, AreaInfo], item I
|
|||
|
||||
// AddAreaItem 将一个成员添加到编排区域中,如果该成员已经存在于编排区域中,则不进行任何操作
|
||||
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
|
||||
}
|
||||
area.items[item.GetID()] = item
|
||||
|
@ -42,12 +41,12 @@ func (slf *Editor[ID, AreaInfo]) AddAreaItem(area *Area[ID, AreaInfo], item Item
|
|||
|
||||
// GetAreas 获取所有的编排区域
|
||||
func (slf *Editor[ID, AreaInfo]) GetAreas() []*Area[ID, AreaInfo] {
|
||||
return slice.Copy(slf.a.areas)
|
||||
return collection.CloneSlice(slf.a.areas)
|
||||
}
|
||||
|
||||
// GetAreasWithScoreAsc 获取所有的编排区域,并按照分数升序排序
|
||||
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 {
|
||||
return areas[i].GetScore(extra...) < areas[j].GetScore(extra...)
|
||||
})
|
||||
|
@ -56,7 +55,7 @@ func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area
|
|||
|
||||
// GetAreasWithScoreDesc 获取所有的编排区域,并按照分数降序排序
|
||||
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 {
|
||||
return areas[i].GetScore(extra...) > areas[j].GetScore(extra...)
|
||||
})
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package buffer
|
||||
|
||||
// NewRing 创建一个环形缓冲区
|
||||
func NewRing[T any](initSize int) *Ring[T] {
|
||||
if initSize <= 1 {
|
||||
panic("initial size must be great than one")
|
||||
// NewRing 创建一个并发不安全的环形缓冲区
|
||||
// - initSize: 初始容量
|
||||
//
|
||||
// 当初始容量小于 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]{
|
||||
buf: make([]T, initSize),
|
||||
initSize: initSize,
|
||||
size: initSize,
|
||||
buf: make([]T, initSize[0]),
|
||||
initSize: initSize[0],
|
||||
size: initSize[0],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,91 +29,120 @@ type Ring[T any] struct {
|
|||
}
|
||||
|
||||
// Read 读取数据
|
||||
func (slf *Ring[T]) Read() (T, error) {
|
||||
func (b *Ring[T]) Read() (T, error) {
|
||||
var t T
|
||||
if slf.r == slf.w {
|
||||
if b.r == b.w {
|
||||
return t, ErrBufferIsEmpty
|
||||
}
|
||||
|
||||
v := slf.buf[slf.r]
|
||||
slf.r++
|
||||
if slf.r == slf.size {
|
||||
slf.r = 0
|
||||
v := b.buf[b.r]
|
||||
b.r++
|
||||
if b.r == b.size {
|
||||
b.r = 0
|
||||
}
|
||||
|
||||
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 查看数据
|
||||
func (slf *Ring[T]) Peek() (t T, err error) {
|
||||
if slf.r == slf.w {
|
||||
func (b *Ring[T]) Peek() (t T, err error) {
|
||||
if b.r == b.w {
|
||||
return t, ErrBufferIsEmpty
|
||||
}
|
||||
|
||||
return slf.buf[slf.r], nil
|
||||
return b.buf[b.r], nil
|
||||
}
|
||||
|
||||
// Write 写入数据
|
||||
func (slf *Ring[T]) Write(v T) {
|
||||
slf.buf[slf.w] = v
|
||||
slf.w++
|
||||
func (b *Ring[T]) Write(v T) {
|
||||
b.buf[b.w] = v
|
||||
b.w++
|
||||
|
||||
if slf.w == slf.size {
|
||||
slf.w = 0
|
||||
if b.w == b.size {
|
||||
b.w = 0
|
||||
}
|
||||
|
||||
if slf.w == slf.r {
|
||||
slf.grow()
|
||||
if b.w == b.r {
|
||||
b.grow()
|
||||
}
|
||||
}
|
||||
|
||||
// grow 扩容
|
||||
func (slf *Ring[T]) grow() {
|
||||
func (b *Ring[T]) grow() {
|
||||
var size int
|
||||
if slf.size < 1024 {
|
||||
size = slf.size * 2
|
||||
if b.size < 1024 {
|
||||
size = b.size * 2
|
||||
} else {
|
||||
size = slf.size + slf.size/4
|
||||
size = b.size + b.size/4
|
||||
}
|
||||
|
||||
buf := make([]T, size)
|
||||
|
||||
copy(buf[0:], slf.buf[slf.r:])
|
||||
copy(buf[slf.size-slf.r:], slf.buf[0:slf.r])
|
||||
copy(buf[0:], b.buf[b.r:])
|
||||
copy(buf[b.size-b.r:], b.buf[0:b.r])
|
||||
|
||||
slf.r = 0
|
||||
slf.w = slf.size
|
||||
slf.size = size
|
||||
slf.buf = buf
|
||||
b.r = 0
|
||||
b.w = b.size
|
||||
b.size = size
|
||||
b.buf = buf
|
||||
}
|
||||
|
||||
// IsEmpty 是否为空
|
||||
func (slf *Ring[T]) IsEmpty() bool {
|
||||
return slf.r == slf.w
|
||||
func (b *Ring[T]) IsEmpty() bool {
|
||||
return b.r == b.w
|
||||
}
|
||||
|
||||
// Cap 返回缓冲区容量
|
||||
func (slf *Ring[T]) Cap() int {
|
||||
return slf.size
|
||||
func (b *Ring[T]) Cap() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
// Len 返回缓冲区长度
|
||||
func (slf *Ring[T]) Len() int {
|
||||
if slf.r == slf.w {
|
||||
func (b *Ring[T]) Len() int {
|
||||
if b.r == b.w {
|
||||
return 0
|
||||
}
|
||||
|
||||
if slf.w > slf.r {
|
||||
return slf.w - slf.r
|
||||
if b.w > b.r {
|
||||
return b.w - b.r
|
||||
}
|
||||
|
||||
return slf.size - slf.r + slf.w
|
||||
return b.size - b.r + b.w
|
||||
}
|
||||
|
||||
// Reset 重置缓冲区
|
||||
func (slf *Ring[T]) Reset() {
|
||||
slf.r = 0
|
||||
slf.w = 0
|
||||
slf.size = slf.initSize
|
||||
slf.buf = make([]T, slf.initSize)
|
||||
func (b *Ring[T]) Reset() {
|
||||
b.r = 0
|
||||
b.w = 0
|
||||
b.size = b.initSize
|
||||
b.buf = make([]T, b.initSize)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -5,15 +5,46 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkUnboundedBuffer(b *testing.B) {
|
||||
ub := buffer.NewUnbounded[int]()
|
||||
func BenchmarkUnbounded_Write(b *testing.B) {
|
||||
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.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
ub.Put(1)
|
||||
<-ub.Get()
|
||||
ub.Load()
|
||||
u.Put(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,22 +1,9 @@
|
|||
package sher
|
||||
package collection
|
||||
|
||||
import (
|
||||
"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 的快捷方式
|
||||
func CloneSlice[S ~[]V, V any](slice S) S {
|
||||
return slices.Clone(slice)
|
||||
|
@ -35,11 +22,14 @@ func CloneMap[M ~map[K]V, K comparable, V any](m M) M {
|
|||
return result
|
||||
}
|
||||
|
||||
// CloneSliceN 克隆切片为 n 个切片进行返回
|
||||
// CloneSliceN 克隆 slice 为 n 个切片进行返回
|
||||
func CloneSliceN[S ~[]V, V any](slice S, n int) []S {
|
||||
if slice == nil {
|
||||
return nil
|
||||
}
|
||||
if n <= 0 {
|
||||
return []S{}
|
||||
}
|
||||
|
||||
var result = make([]S, n)
|
||||
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
|
||||
}
|
||||
|
||||
if n <= 0 {
|
||||
return []M{}
|
||||
}
|
||||
|
||||
var result = make([]M, n)
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = CloneMap(m)
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package collection 用于对 input 和 map 操作的工具函数
|
||||
package collection
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,17 +1,20 @@
|
|||
package sher
|
||||
package collection
|
||||
|
||||
// FilterOutByIndices 过滤切片中特定索引的元素,返回过滤后的切片
|
||||
func FilterOutByIndices[S ~[]V, V any](slice S, indices ...int) S {
|
||||
if slice == nil {
|
||||
return nil
|
||||
}
|
||||
if len(indices) == 0 {
|
||||
func FilterOutByIndices[S []V, V any](slice S, indices ...int) S {
|
||||
if slice == nil || len(slice) == 0 || len(indices) == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
excludeMap := make(map[int]bool)
|
||||
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))
|
||||
|
@ -97,7 +100,7 @@ func FilterOutByKeys[M ~map[K]V, K comparable, V any](m M, keys ...K) M {
|
|||
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 {
|
||||
if m == 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))
|
||||
for k, v := range m {
|
||||
if condition(k, v) {
|
||||
if !condition(k, v) {
|
||||
validMap[k] = v
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package slice
|
||||
package listings
|
||||
|
||||
// NewPagedSlice 创建一个新的 PagedSlice 实例。
|
||||
func NewPagedSlice[T any](pageSize int) *PagedSlice[T] {
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package sher
|
||||
package collection
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ func MappingFromSlice[S ~[]V, NS ~[]N, V, N any](slice S, handler func(value V)
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package sher
|
||||
package collection
|
||||
|
||||
// MergeSlices 合并切片
|
||||
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
|
||||
func MergeMaps[M ~map[K]V, K comparable, V any](maps ...M) (result M) {
|
||||
if len(maps) == 0 {
|
||||
return nil
|
||||
return make(M)
|
||||
}
|
||||
|
||||
var length int
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,70 +1,67 @@
|
|||
package sher
|
||||
package collection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
)
|
||||
|
||||
// ChooseRandomSliceElementRepeatN 获取切片中的 n 个随机元素,允许重复
|
||||
// - 如果 n 大于切片长度或小于 0 时将会发生 panic
|
||||
// ChooseRandomSliceElementRepeatN 返回 slice 中的 n 个可重复随机元素
|
||||
// - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil
|
||||
func ChooseRandomSliceElementRepeatN[S ~[]V, V any](slice S, n int) (result []V) {
|
||||
if slice == nil {
|
||||
if len(slice) == 0 || n <= 0 {
|
||||
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)
|
||||
m := len(slice) - 1
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = slice[random.Int(0, len(slice)-1)]
|
||||
result[i] = slice[random.Int(0, m)]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ChooseRandomIndexRepeatN 获取切片中的 n 个随机元素的索引,允许重复
|
||||
// - 如果 n 大于切片长度或小于 0 时将会发生 panic
|
||||
// ChooseRandomIndexRepeatN 返回 slice 中的 n 个可重复随机元素的索引
|
||||
// - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil
|
||||
func ChooseRandomIndexRepeatN[S ~[]V, V any](slice S, n int) (result []int) {
|
||||
if slice == nil {
|
||||
if len(slice) == 0 || n <= 0 {
|
||||
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)
|
||||
m := len(slice) - 1
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = random.Int(0, len(slice)-1)
|
||||
result[i] = random.Int(0, m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ChooseRandomSliceElement 获取切片中的随机元素
|
||||
// ChooseRandomSliceElement 返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值
|
||||
func ChooseRandomSliceElement[S ~[]V, V any](slice S) (v V) {
|
||||
if slice == nil {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
return slice[random.Int(0, len(slice)-1)]
|
||||
}
|
||||
|
||||
// ChooseRandomIndex 获取切片中的随机元素的索引
|
||||
// ChooseRandomIndex 返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1
|
||||
func ChooseRandomIndex[S ~[]V, V any](slice S) (index int) {
|
||||
if slice == nil {
|
||||
return
|
||||
if len(slice) == 0 {
|
||||
return -1
|
||||
}
|
||||
return random.Int(0, len(slice)-1)
|
||||
}
|
||||
|
||||
// ChooseRandomSliceElementN 获取切片中的 n 个随机元素
|
||||
// - 如果 n 大于切片长度或小于 0 时将会发生 panic
|
||||
// ChooseRandomSliceElementN 返回 slice 中的 n 个不可重复的随机元素
|
||||
// - 当 slice 长度为 0 或 n 大于 slice 长度或小于 0 时将会发生 panic
|
||||
func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
if n > len(slice) || n < 0 {
|
||||
if len(slice) == 0 || n <= 0 || 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)
|
||||
for i := 0; i < n; i++ {
|
||||
result[i] = slice[random.Int(0, len(slice)-1)]
|
||||
result = make([]V, 0, n)
|
||||
valid := ConvertSliceToIndexOnlyMap(slice)
|
||||
for i := range valid {
|
||||
result = append(result, slice[i])
|
||||
if len(result) == n {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -72,11 +69,11 @@ func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) {
|
|||
// ChooseRandomIndexN 获取切片中的 n 个随机元素的索引
|
||||
// - 如果 n 大于切片长度或小于 0 时将会发生 panic
|
||||
func ChooseRandomIndexN[S ~[]V, V any](slice S, n int) (result []int) {
|
||||
if slice == nil {
|
||||
if len(slice) == 0 {
|
||||
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("inputN is greater than the length of the input or less than 0, inputN: %d, length: %d", n, len(slice)))
|
||||
}
|
||||
result = make([]int, n)
|
||||
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
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// ChooseRandomMapValueRepeatN 获取 map 中的 n 个随机 value,允许重复
|
||||
// ChooseRandomMapValueRepeatN 获取 map 中的 n 个随机 n,允许重复
|
||||
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
|
||||
func ChooseRandomMapValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (result []V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 n 个随机 key 和 value,允许重复
|
||||
// ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 n 个随机 key 和 v,允许重复
|
||||
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
|
||||
func ChooseRandomMapKeyAndValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) M {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// ChooseRandomMapKeyN 获取 map 中的 n 个随机 key
|
||||
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
|
||||
// ChooseRandomMapKeyN 获取 map 中的 inputN 个随机 key
|
||||
// - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic
|
||||
func ChooseRandomMapKeyN[M ~map[K]V, K comparable, V any](m M, n int) (result []K) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
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)
|
||||
i := 0
|
||||
|
@ -192,7 +189,7 @@ func ChooseRandomMapValueN[M ~map[K]V, K comparable, V any](m M, n int) (result
|
|||
return
|
||||
}
|
||||
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)
|
||||
i := 0
|
||||
|
@ -206,7 +203,7 @@ func ChooseRandomMapValueN[M ~map[K]V, K comparable, V any](m M, n int) (result
|
|||
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) {
|
||||
if m == nil {
|
||||
return
|
||||
|
@ -217,14 +214,14 @@ func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V)
|
|||
return
|
||||
}
|
||||
|
||||
// ChooseRandomMapKeyAndValueN 获取 map 中的 n 个随机 key 和 value
|
||||
// ChooseRandomMapKeyAndValueN 获取 map 中的 inputN 个随机 key 和 v
|
||||
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
|
||||
func ChooseRandomMapKeyAndValueN[M ~map[K]V, K comparable, V any](m M, n int) M {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
i := 0
|
|
@ -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]
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package sher
|
||||
package collection
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
|
@ -6,6 +6,16 @@ import (
|
|||
"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 对切片进行降序排序
|
||||
func Desc[S ~[]V, V any, Sort generic.Ordered](slice *S, getter func(index int) Sort) {
|
||||
sort.Slice(*slice, func(i, j int) bool {
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ func WithMatcherEvaluation[T Item](evaluate func(items []T) float64) MatcherOpti
|
|||
func WithMatcherLeastLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[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] {
|
||||
return func(m *Matcher[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] {
|
||||
return func(m *Matcher[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] {
|
||||
return func(m *Matcher[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]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := slice.LimitedCombinations(items, count, count)
|
||||
groups := collection.FindCombinationsInSliceByRange(items, count, count)
|
||||
for _, items := range groups {
|
||||
if count > 0 && len(items) != count {
|
||||
continue
|
||||
|
@ -147,8 +147,12 @@ func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T
|
|||
if len(group) != n {
|
||||
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 {
|
||||
var t E
|
||||
var init = true
|
||||
|
@ -163,7 +167,7 @@ func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T
|
|||
}
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
combinations = append(combinations, slice.Merge(group, otherGroup))
|
||||
combinations = append(combinations, collection.MergeSlices(group, otherGroup))
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue