diff --git a/game/activity/controller_internal.go b/game/activity/controller_internal.go index 5ef7e17..802fc60 100644 --- a/game/activity/controller_internal.go +++ b/game/activity/controller_internal.go @@ -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) diff --git a/game/activity/events.go b/game/activity/events.go index d9cf694..ad4f58f 100644 --- a/game/activity/events.go +++ b/game/activity/events.go @@ -2,9 +2,10 @@ package activity import ( "fmt" + "github.com/kercylan98/minotaur/utils/collection" + listings2 "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]*listings2.PrioritySlice[func(activityId any)] // 即将开始的活动事件处理器 + startedEventHandlers map[any]*listings2.PrioritySlice[func(activityId any)] // 活动开始事件处理器 + endedEventHandlers map[any]*listings2.PrioritySlice[func(activityId any)] // 活动结束事件处理器 + extShowStartedEventHandlers map[any]*listings2.PrioritySlice[func(activityId any)] // 活动结束后延长展示开始事件处理器 + extShowEndedEventHandlers map[any]*listings2.PrioritySlice[func(activityId any)] // 活动结束后延长展示结束事件处理器 + newDayEventHandlers map[any]*listings2.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]*listings2.PrioritySlice[func(activityId any)]) + startedEventHandlers = make(map[any]*listings2.PrioritySlice[func(activityId any)]) + endedEventHandlers = make(map[any]*listings2.PrioritySlice[func(activityId any)]) + extShowStartedEventHandlers = make(map[any]*listings2.PrioritySlice[func(activityId any)]) + extShowEndedEventHandlers = make(map[any]*listings2.PrioritySlice[func(activityId any)]) + newDayEventHandlers = make(map[any]*listings2.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 = listings2.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 = listings2.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 = listings2.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 = listings2.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 = listings2.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 = listings2.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 新的一天事件 diff --git a/game/space/room_controller.go b/game/space/room_controller.go index e87a3d4..6eabd01 100644 --- a/game/space/room_controller.go +++ b/game/space/room_controller.go @@ -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 { diff --git a/game/space/room_manager.go b/game/space/room_manager.go index 1195154..34518e6 100644 --- a/game/space/room_manager.go +++ b/game/space/room_manager.go @@ -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...) diff --git a/game/task/task_test.go b/game/task/task_test.go index 55f3c4e..e55f144 100644 --- a/game/task/task_test.go +++ b/game/task/task_test.go @@ -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) diff --git a/planner/pce/exporter/cmd/exportgo.go b/planner/pce/exporter/cmd/exportgo.go index fde1993..973d254 100644 --- a/planner/pce/exporter/cmd/exportgo.go +++ b/planner/pce/exporter/cmd/exportgo.go @@ -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 { diff --git a/planner/pce/exporter/cmd/exportjson.go b/planner/pce/exporter/cmd/exportjson.go index 1b1854e..2d42e23 100644 --- a/planner/pce/exporter/cmd/exportjson.go +++ b/planner/pce/exporter/cmd/exportjson.go @@ -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 { diff --git a/planner/pce/exporter/cmd/exportjson_test.go b/planner/pce/exporter/cmd/exportjson_test.go index 5f18762..742d4d0 100644 --- a/planner/pce/exporter/cmd/exportjson_test.go +++ b/planner/pce/exporter/cmd/exportjson_test.go @@ -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 { diff --git a/planner/pce/tmpl_field.go b/planner/pce/tmpl_field.go index 3c67ce6..27f3264 100644 --- a/planner/pce/tmpl_field.go +++ b/planner/pce/tmpl_field.go @@ -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.FindInMapKey(fields, t) { slf.Struct = nil slf.Type = t } else { diff --git a/planner/pce/tmpl_struct.go b/planner/pce/tmpl_struct.go index 11d727b..64bec2f 100644 --- a/planner/pce/tmpl_struct.go +++ b/planner/pce/tmpl_struct.go @@ -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.FindInMapKey(fields, fieldType) { field.setStruct(parent, name, desc, fieldType, fields) } else { field.Type = GetFieldGolangType(fields[fieldType]) diff --git a/server/bot_test.go b/server/bot_test.go index 94d10d8..11eb3ed 100644 --- a/server/bot_test.go +++ b/server/bot_test.go @@ -45,5 +45,5 @@ func TestNewBot(t *testing.T) { bot.SendPacket([]byte("hello")) }) - srv.Run(":9600") + _ = srv.Run(":9600") } diff --git a/server/conn.go b/server/conn.go index de38c4c..1672fd6 100644 --- a/server/conn.go +++ b/server/conn.go @@ -6,8 +6,8 @@ import ( "fmt" "github.com/gorilla/websocket" "github.com/kercylan98/minotaur/server/writeloop" + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/concurrent" - "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/timer" @@ -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 设置消息数据,该数据将在消息处理完成后释放 diff --git a/server/event.go b/server/event.go index be7d267..9bdcc4f 100644 --- a/server/event.go +++ b/server/event.go @@ -2,9 +2,10 @@ package server import ( "fmt" + "github.com/kercylan98/minotaur/utils/collection" + listings2 "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: listings2.NewPrioritySlice[StartBeforeEventHandler](), + startFinishEventHandlers: listings2.NewPrioritySlice[StartFinishEventHandler](), + stopEventHandlers: listings2.NewPrioritySlice[StopEventHandler](), + connectionReceivePacketEventHandlers: listings2.NewPrioritySlice[ConnectionReceivePacketEventHandler](), + connectionOpenedEventHandlers: listings2.NewPrioritySlice[ConnectionOpenedEventHandler](), + connectionClosedEventHandlers: listings2.NewPrioritySlice[ConnectionClosedEventHandler](), + messageErrorEventHandlers: listings2.NewPrioritySlice[MessageErrorEventHandler](), + messageLowExecEventHandlers: listings2.NewPrioritySlice[MessageLowExecEventHandler](), + connectionOpenedAfterEventHandlers: listings2.NewPrioritySlice[ConnectionOpenedAfterEventHandler](), + connectionWritePacketBeforeHandlers: listings2.NewPrioritySlice[ConnectionWritePacketBeforeEventHandler](), + shuntChannelCreatedEventHandlers: listings2.NewPrioritySlice[ShuntChannelCreatedEventHandler](), + shuntChannelClosedEventHandlers: listings2.NewPrioritySlice[ShuntChannelClosedEventHandler](), + connectionPacketPreprocessEventHandlers: listings2.NewPrioritySlice[ConnectionPacketPreprocessEventHandler](), + messageExecBeforeEventHandlers: listings2.NewPrioritySlice[MessageExecBeforeEventHandler](), + messageReadyEventHandlers: listings2.NewPrioritySlice[MessageReadyEventHandler](), + deadlockDetectEventHandlers: listings2.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 *listings2.PrioritySlice[StartBeforeEventHandler] + startFinishEventHandlers *listings2.PrioritySlice[StartFinishEventHandler] + stopEventHandlers *listings2.PrioritySlice[StopEventHandler] + connectionReceivePacketEventHandlers *listings2.PrioritySlice[ConnectionReceivePacketEventHandler] + connectionOpenedEventHandlers *listings2.PrioritySlice[ConnectionOpenedEventHandler] + connectionClosedEventHandlers *listings2.PrioritySlice[ConnectionClosedEventHandler] + messageErrorEventHandlers *listings2.PrioritySlice[MessageErrorEventHandler] + messageLowExecEventHandlers *listings2.PrioritySlice[MessageLowExecEventHandler] + connectionOpenedAfterEventHandlers *listings2.PrioritySlice[ConnectionOpenedAfterEventHandler] + connectionWritePacketBeforeHandlers *listings2.PrioritySlice[ConnectionWritePacketBeforeEventHandler] + shuntChannelCreatedEventHandlers *listings2.PrioritySlice[ShuntChannelCreatedEventHandler] + shuntChannelClosedEventHandlers *listings2.PrioritySlice[ShuntChannelClosedEventHandler] + connectionPacketPreprocessEventHandlers *listings2.PrioritySlice[ConnectionPacketPreprocessEventHandler] + messageExecBeforeEventHandlers *listings2.PrioritySlice[MessageExecBeforeEventHandler] + messageReadyEventHandlers *listings2.PrioritySlice[MessageReadyEventHandler] + deadlockDetectEventHandlers *listings2.PrioritySlice[OnDeadlockDetectEventHandler] - consoleCommandEventHandlers map[string]*slice.Priority[ConsoleCommandEventHandler] + consoleCommandEventHandlers map[string]*listings2.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]*listings2.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 = listings2.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())) } @@ -226,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())) } @@ -246,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())) } @@ -262,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())) } @@ -284,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())) } @@ -305,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())) } @@ -323,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())) } @@ -341,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())) } @@ -356,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())) } @@ -378,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())) } @@ -402,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())) } @@ -426,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() { @@ -447,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) { diff --git a/server/gateway/events.go b/server/gateway/events.go index edfa1ce..a2ed168 100644 --- a/server/gateway/events.go +++ b/server/gateway/events.go @@ -2,7 +2,8 @@ package gateway import ( "github.com/kercylan98/minotaur/server" - "github.com/kercylan98/minotaur/utils/slice" + "github.com/kercylan98/minotaur/utils/collection" + listings2 "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: listings2.NewPrioritySlice[ConnectionOpenedEventHandle](), + connectionClosedEventHandles: listings2.NewPrioritySlice[ConnectionClosedEventHandle](), + connectionReceivePacketEventHandles: listings2.NewPrioritySlice[ConnectionReceivePacketEventHandle](), + endpointConnectOpenedEventHandles: listings2.NewPrioritySlice[EndpointConnectOpenedEventHandle](), + endpointConnectClosedEventHandles: listings2.NewPrioritySlice[EndpointConnectClosedEventHandle](), + endpointConnectReceivePacketEventHandles: listings2.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 *listings2.PrioritySlice[ConnectionOpenedEventHandle] + connectionClosedEventHandles *listings2.PrioritySlice[ConnectionClosedEventHandle] + connectionReceivePacketEventHandles *listings2.PrioritySlice[ConnectionReceivePacketEventHandle] + endpointConnectOpenedEventHandles *listings2.PrioritySlice[EndpointConnectOpenedEventHandle] + endpointConnectClosedEventHandles *listings2.PrioritySlice[EndpointConnectClosedEventHandle] + endpointConnectReceivePacketEventHandles *listings2.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) { diff --git a/server/hub.go b/server/hub.go index 4c31ba9..cd56dfb 100644 --- a/server/hub.go +++ b/server/hub.go @@ -2,7 +2,7 @@ package server import ( "context" - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/collection" "sync" ) @@ -96,7 +96,7 @@ func (h *hub) IsOnline(id string) bool { // GetOnlineAll 获取所有在线连接 func (h *hub) GetOnlineAll() map[string]*Conn { h.chanMutex.RLock() - cop := hash.Copy(h.connections) + cop := collection.CloneMap(h.connections) h.chanMutex.RUnlock() return cop } diff --git a/server/lockstep/lockstep.go b/server/lockstep/lockstep.go index e968111..49a5376 100644 --- a/server/lockstep/lockstep.go +++ b/server/lockstep/lockstep.go @@ -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) } diff --git a/server/message.go b/server/message.go index ed48ecc..7b28a2b 100644 --- a/server/message.go +++ b/server/message.go @@ -1,7 +1,7 @@ package server import ( - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/super" ) @@ -70,7 +70,7 @@ type ( // HasMessageType 检查是否存在指定的消息类型 func HasMessageType(mt MessageType) bool { - return hash.Exist(messageNames, mt) + return collection.FindInMapKey(messageNames, mt) } // Message 服务器消息 diff --git a/server/network.go b/server/network.go index e9a6ccf..c2b8e49 100644 --- a/server/network.go +++ b/server/network.go @@ -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" @@ -53,12 +52,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.FindInMapKey(networkNameMap, string(n)) { panic(fmt.Errorf("unsupported network mode: %s", n)) } } diff --git a/server/server.go b/server/server.go index 38503c0..c946cbb 100644 --- a/server/server.go +++ b/server/server.go @@ -7,10 +7,10 @@ import ( "github.com/gin-gonic/gin" "github.com/kercylan98/minotaur/server/internal/dispatcher" "github.com/kercylan98/minotaur/server/internal/logger" + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/concurrent" "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" @@ -362,7 +362,7 @@ 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.ConvertSliceToAny(fields)...) + log.Warn("ServerLowMessage", collection.ConvertSliceToAny(fields)...) srv.OnMessageLowExecEvent(message, cost) } } diff --git a/utils/aoi/2d.go b/utils/aoi/2d.go index 631b2cd..3eb15aa 100644 --- a/utils/aoi/2d.go +++ b/utils/aoi/2d.go @@ -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) { diff --git a/utils/arrangement/area.go b/utils/arrangement/area.go index 0283d95..1b8e3a0 100644 --- a/utils/arrangement/area.go +++ b/utils/arrangement/area.go @@ -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.FindInMapKey(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.FindInMapKey(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 } diff --git a/utils/arrangement/arrangement.go b/utils/arrangement/arrangement.go index 188e4cb..bd19d1b 100644 --- a/utils/arrangement/arrangement.go +++ b/utils/arrangement/arrangement.go @@ -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 { diff --git a/utils/arrangement/editor.go b/utils/arrangement/editor.go index 3a80bd3..750ce63 100644 --- a/utils/arrangement/editor.go +++ b/utils/arrangement/editor.go @@ -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.FindInMapKey(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...) }) diff --git a/utils/sher/clone.go b/utils/collection/clone.go similarity index 81% rename from utils/sher/clone.go rename to utils/collection/clone.go index 7586af6..69a7c0d 100644 --- a/utils/sher/clone.go +++ b/utils/collection/clone.go @@ -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) diff --git a/utils/collection/clone_example_test.go b/utils/collection/clone_example_test.go new file mode 100644 index 0000000..e5e00ed --- /dev/null +++ b/utils/collection/clone_example_test.go @@ -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 +} diff --git a/utils/collection/clone_test.go b/utils/collection/clone_test.go new file mode 100644 index 0000000..1fc2803 --- /dev/null +++ b/utils/collection/clone_test.go @@ -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") + } + } + } + }) + } +} diff --git a/utils/collection/contains.go b/utils/collection/contains.go new file mode 100644 index 0000000..52878e3 --- /dev/null +++ b/utils/collection/contains.go @@ -0,0 +1,331 @@ +package collection + +type ComparisonHandler[V any] func(source, target V) bool + +// 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 +} diff --git a/utils/collection/contains_example_test.go b/utils/collection/contains_example_test.go new file mode 100644 index 0000000..3b96d36 --- /dev/null +++ b/utils/collection/contains_example_test.go @@ -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 +} diff --git a/utils/collection/contains_test.go b/utils/collection/contains_test.go new file mode 100644 index 0000000..2c339c3 --- /dev/null +++ b/utils/collection/contains_test.go @@ -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) + } + }) + } +} diff --git a/utils/sher/convert.go b/utils/collection/convert.go similarity index 58% rename from utils/sher/convert.go rename to utils/collection/convert.go index 18cf47c..4333f66 100644 --- a/utils/sher/convert.go +++ b/utils/collection/convert.go @@ -1,7 +1,10 @@ -package sher +package collection // ConvertSliceToAny 将切片转换为任意类型的切片 func ConvertSliceToAny[S ~[]V, V any](s S) []any { + if s == nil { + return nil + } var r = make([]any, len(s)) for i, v := range s { r[i] = v @@ -11,6 +14,9 @@ func ConvertSliceToAny[S ~[]V, V any](s S) []any { // ConvertSliceToIndexMap 将切片转换为索引为键的映射 func ConvertSliceToIndexMap[S ~[]V, V any](s S) map[int]V { + if s == nil { + return nil + } var r = make(map[int]V, len(s)) for i, v := range s { r[i] = v @@ -20,6 +26,9 @@ func ConvertSliceToIndexMap[S ~[]V, V any](s S) map[int]V { // ConvertSliceToMap 将切片转换为值为键的映射 func ConvertSliceToMap[S ~[]V, V comparable](s S) map[V]struct{} { + if s == nil { + return nil + } var r = make(map[V]struct{}, len(s)) for _, v := range s { r[v] = struct{}{} @@ -29,6 +38,9 @@ func ConvertSliceToMap[S ~[]V, V comparable](s S) map[V]struct{} { // ConvertSliceToBoolMap 将切片转换为值为键的映射 func ConvertSliceToBoolMap[S ~[]V, V comparable](s S) map[V]bool { + if s == nil { + return nil + } var r = make(map[V]bool, len(s)) for _, v := range s { r[v] = true @@ -38,6 +50,9 @@ func ConvertSliceToBoolMap[S ~[]V, V comparable](s S) map[V]bool { // ConvertMapKeysToSlice 将映射的键转换为切片 func ConvertMapKeysToSlice[M ~map[K]V, K comparable, V any](m M) []K { + if m == nil { + return nil + } var r = make([]K, 0, len(m)) for k := range m { r = append(r, k) @@ -47,9 +62,48 @@ func ConvertMapKeysToSlice[M ~map[K]V, K comparable, V any](m M) []K { // ConvertMapValuesToSlice 将映射的值转换为切片 func ConvertMapValuesToSlice[M ~map[K]V, K comparable, V any](m M) []V { + if m == nil { + 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] + } +} diff --git a/utils/collection/convert_example_test.go b/utils/collection/convert_example_test.go new file mode 100644 index 0000000..10c48ea --- /dev/null +++ b/utils/collection/convert_example_test.go @@ -0,0 +1 @@ +package collection_test diff --git a/utils/collection/convert_test.go b/utils/collection/convert_test.go new file mode 100644 index 0000000..006a8a1 --- /dev/null +++ b/utils/collection/convert_test.go @@ -0,0 +1,264 @@ +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 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) + } + } + }) + } +} diff --git a/utils/collection/doc.go b/utils/collection/doc.go new file mode 100644 index 0000000..50528d9 --- /dev/null +++ b/utils/collection/doc.go @@ -0,0 +1,2 @@ +// Package collection 用于对 input 和 map 操作的工具函数 +package collection diff --git a/utils/collection/drop.go b/utils/collection/drop.go new file mode 100644 index 0000000..d6c1e49 --- /dev/null +++ b/utils/collection/drop.go @@ -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 +} diff --git a/utils/collection/drop_example_test.go b/utils/collection/drop_example_test.go new file mode 100644 index 0000000..490e7c5 --- /dev/null +++ b/utils/collection/drop_example_test.go @@ -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] +} diff --git a/utils/collection/drop_test.go b/utils/collection/drop_test.go new file mode 100644 index 0000000..b4540e9 --- /dev/null +++ b/utils/collection/drop_test.go @@ -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") + } + } + }) + } +} diff --git a/utils/sher/duplicate.go b/utils/collection/duplicate.go similarity index 98% rename from utils/sher/duplicate.go rename to utils/collection/duplicate.go index 0d0c699..5f6907c 100644 --- a/utils/sher/duplicate.go +++ b/utils/collection/duplicate.go @@ -1,4 +1,4 @@ -package sher +package collection // DeduplicateSliceInPlace 去除切片中的重复元素 func DeduplicateSliceInPlace[S ~[]V, V comparable](s *S) { diff --git a/utils/collection/duplicate_example_test.go b/utils/collection/duplicate_example_test.go new file mode 100644 index 0000000..026903c --- /dev/null +++ b/utils/collection/duplicate_example_test.go @@ -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] +} diff --git a/utils/collection/duplicate_test.go b/utils/collection/duplicate_test.go new file mode 100644 index 0000000..2632441 --- /dev/null +++ b/utils/collection/duplicate_test.go @@ -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) + } + } + }) + } +} diff --git a/utils/sher/filter.go b/utils/collection/filter.go similarity index 88% rename from utils/sher/filter.go rename to utils/collection/filter.go index 347ca52..57ceab7 100644 --- a/utils/sher/filter.go +++ b/utils/collection/filter.go @@ -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 } } diff --git a/utils/collection/filter_example_test.go b/utils/collection/filter_example_test.go new file mode 100644 index 0000000..cd80fac --- /dev/null +++ b/utils/collection/filter_example_test.go @@ -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] +} diff --git a/utils/collection/filter_test.go b/utils/collection/filter_test.go new file mode 100644 index 0000000..edc1d55 --- /dev/null +++ b/utils/collection/filter_test.go @@ -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") + } + } + }) + } +} diff --git a/utils/collection/find.go b/utils/collection/find.go new file mode 100644 index 0000000..c8e1be7 --- /dev/null +++ b/utils/collection/find.go @@ -0,0 +1,256 @@ +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 +} + +// FindInSliceByBinary 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 +func FindInSliceByBinary[S ~[]V, V any](slice S, handler func(v V) bool) (i int, t V) { + low := 0 + high := len(slice) - 1 + + for low <= high { + mid := low + (high-low)/2 + if handler(slice[mid]) { + return mid, slice[mid] + } else if handler(slice[mid]) { + high = mid - 1 + } else { + low = mid + 1 + } + } + return -1, t +} + +// FindMinimumInSlice 获取切片中的最小值 +func FindMinimumInSlice[S ~[]V, V generic.Number](slice S, handler ComparisonHandler[V]) (result V) { + if len(slice) == 0 { + return + } + result = slice[0] + for i := 1; i < len(slice); i++ { + if handler(slice[i], result) { + result = slice[i] + } + } + return +} + +// FindMaximumInSlice 获取切片中的最大值 +func FindMaximumInSlice[S ~[]V, V generic.Number](slice S, handler ComparisonHandler[V]) (result V) { + if len(slice) == 0 { + return + } + result = slice[0] + for i := 1; i < len(slice); i++ { + if handler(result, slice[i]) { + result = slice[i] + } + } + return +} + +// FindMin2MaxInSlice 获取切片中的最小值和最大值 +func FindMin2MaxInSlice[S ~[]V, V generic.Number](slice S, handler ComparisonHandler[V]) (min, max V) { + if len(slice) == 0 { + return + } + min = slice[0] + max = slice[0] + for i := 1; i < len(slice); i++ { + if handler(slice[i], min) { + min = slice[i] + } + if handler(max, slice[i]) { + max = slice[i] + } + } + return +} + +// FindMinFromMap 获取 map 中的最小值 +func FindMinFromMap[M ~map[K]V, K comparable, V generic.Number](m M, handler ComparisonHandler[V]) (result V) { + if m == nil { + return + } + for _, v := range m { + if handler(v, result) { + result = v + } + } + return +} + +// FindMaxFromMap 获取 map 中的最大值 +func FindMaxFromMap[M ~map[K]V, K comparable, V generic.Number](m M, handler ComparisonHandler[V]) (result V) { + if m == nil { + return + } + for _, v := range m { + if handler(result, v) { + result = v + } + } + return +} + +// FindMin2MaxFromMap 获取 map 中的最小值和最大值 +func FindMin2MaxFromMap[M ~map[K]V, K comparable, V generic.Number](m M, handler ComparisonHandler[V]) (min, max V) { + if m == nil { + return + } + for _, v := range m { + if handler(v, min) { + min = v + } + if handler(max, v) { + max = v + } + } + return +} + +// FindInMapKey 判断 map 中是否存在某个 key +func FindInMapKey[M ~map[K]V, K comparable, V any](m M, k K) bool { + _, exist := m[k] + return exist +} diff --git a/utils/collection/find_example_test.go b/utils/collection/find_example_test.go new file mode 100644 index 0000000..c0163e3 --- /dev/null +++ b/utils/collection/find_example_test.go @@ -0,0 +1,50 @@ +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 +} diff --git a/utils/collection/find_test.go b/utils/collection/find_test.go new file mode 100644 index 0000000..5a96795 --- /dev/null +++ b/utils/collection/find_test.go @@ -0,0 +1,129 @@ +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") + } + }) + } +} diff --git a/utils/collection/item.go b/utils/collection/item.go new file mode 100644 index 0000000..872bf7e --- /dev/null +++ b/utils/collection/item.go @@ -0,0 +1,6 @@ +package collection + +// SwapSlice 将切片中的两个元素进行交换 +func SwapSlice[S ~[]V, V any](slice *S, i, j int) { + (*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i] +} diff --git a/utils/collection/listings/matrix.go b/utils/collection/listings/matrix.go new file mode 100644 index 0000000..6b5fc16 --- /dev/null +++ b/utils/collection/listings/matrix.go @@ -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)) +} diff --git a/utils/slice/paged_slice.go b/utils/collection/listings/paged_slice.go similarity index 98% rename from utils/slice/paged_slice.go rename to utils/collection/listings/paged_slice.go index 31da7e7..d58c29d 100644 --- a/utils/slice/paged_slice.go +++ b/utils/collection/listings/paged_slice.go @@ -1,4 +1,4 @@ -package slice +package listings // NewPagedSlice 创建一个新的 PagedSlice 实例。 func NewPagedSlice[T any](pageSize int) *PagedSlice[T] { diff --git a/utils/collection/listings/priority_slice.go b/utils/collection/listings/priority_slice.go new file mode 100644 index 0000000..b1e3f1b --- /dev/null +++ b/utils/collection/listings/priority_slice.go @@ -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()) + }) +} + +// Slice 返回切片 +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 +} diff --git a/utils/collection/listings/priority_slice_test.go b/utils/collection/listings/priority_slice_test.go new file mode 100644 index 0000000..c8d3ac8 --- /dev/null +++ b/utils/collection/listings/priority_slice_test.go @@ -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) +} diff --git a/utils/sher/map.go b/utils/collection/map.go similarity index 96% rename from utils/sher/map.go rename to utils/collection/map.go index 75d0459..10b3eeb 100644 --- a/utils/sher/map.go +++ b/utils/collection/map.go @@ -1,4 +1,4 @@ -package sher +package collection // MappingFromSlice 将切片中的元素进行转换 func MappingFromSlice[S ~[]V, NS ~[]N, V, N any](slice S, handler func(value V) N) NS { diff --git a/utils/sher/merge.go b/utils/collection/merge.go similarity index 98% rename from utils/sher/merge.go rename to utils/collection/merge.go index 4100abe..89a4757 100644 --- a/utils/sher/merge.go +++ b/utils/collection/merge.go @@ -1,4 +1,4 @@ -package sher +package collection // MergeSlices 合并切片 func MergeSlices[S ~[]V, V any](slices ...S) (result S) { diff --git a/utils/sher/random.go b/utils/collection/random.go similarity index 55% rename from utils/sher/random.go rename to utils/collection/random.go index ad360fb..567eec0 100644 --- a/utils/sher/random.go +++ b/utils/collection/random.go @@ -1,18 +1,18 @@ -package sher +package collection import ( "fmt" "github.com/kercylan98/minotaur/utils/random" ) -// ChooseRandomSliceElementRepeatN 获取切片中的 n 个随机元素,允许重复 -// - 如果 n 大于切片长度或小于 0 时将会发生 panic +// ChooseRandomSliceElementRepeatN 获取切片中的 inputN 个随机元素,允许重复 +// - 如果 inputN 大于切片长度或小于 0 时将会发生 panic func ChooseRandomSliceElementRepeatN[S ~[]V, V any](slice S, n int) (result []V) { if slice == nil { 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([]V, n) for i := 0; i < n; i++ { @@ -21,14 +21,14 @@ func ChooseRandomSliceElementRepeatN[S ~[]V, V any](slice S, n int) (result []V) return } -// ChooseRandomIndexRepeatN 获取切片中的 n 个随机元素的索引,允许重复 -// - 如果 n 大于切片长度或小于 0 时将会发生 panic +// ChooseRandomIndexRepeatN 获取切片中的 inputN 个随机元素的索引,允许重复 +// - 如果 inputN 大于切片长度或小于 0 时将会发生 panic func ChooseRandomIndexRepeatN[S ~[]V, V any](slice S, n int) (result []int) { if slice == nil { 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++ { @@ -53,14 +53,14 @@ func ChooseRandomIndex[S ~[]V, V any](slice S) (index int) { return random.Int(0, len(slice)-1) } -// ChooseRandomSliceElementN 获取切片中的 n 个随机元素 -// - 如果 n 大于切片长度或小于 0 时将会发生 panic +// ChooseRandomSliceElementN 获取切片中的 inputN 个随机元素 +// - 如果 inputN 大于切片长度或小于 0 时将会发生 panic func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) { if slice == nil { 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([]V, n) for i := 0; i < n; i++ { @@ -69,14 +69,14 @@ func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) { return } -// ChooseRandomIndexN 获取切片中的 n 个随机元素的索引 -// - 如果 n 大于切片长度或小于 0 时将会发生 panic +// ChooseRandomIndexN 获取切片中的 inputN 个随机元素的索引 +// - 如果 inputN 大于切片长度或小于 0 时将会发生 panic func ChooseRandomIndexN[S ~[]V, V any](slice S, n int) (result []int) { if slice == nil { 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++ { @@ -85,14 +85,14 @@ func ChooseRandomIndexN[S ~[]V, V any](slice S, n int) (result []int) { return } -// ChooseRandomMapKeyRepeatN 获取 map 中的 n 个随机 key,允许重复 -// - 如果 n 大于 map 长度或小于 0 时将会发生 panic +// ChooseRandomMapKeyRepeatN 获取 map 中的 inputN 个随机 key,允许重复 +// - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic func ChooseRandomMapKeyRepeatN[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) for i := 0; i < n; i++ { @@ -104,14 +104,14 @@ func ChooseRandomMapKeyRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (res return } -// ChooseRandomMapValueRepeatN 获取 map 中的 n 个随机 value,允许重复 -// - 如果 n 大于 map 长度或小于 0 时将会发生 panic +// ChooseRandomMapValueRepeatN 获取 map 中的 inputN 个随机 inputV,允许重复 +// - 如果 inputN 大于 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 +123,14 @@ func ChooseRandomMapValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (r return } -// ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 n 个随机 key 和 value,允许重复 -// - 如果 n 大于 map 长度或小于 0 时将会发生 panic +// ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 inputN 个随机 key 和 inputV,允许重复 +// - 如果 inputN 大于 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++ { @@ -153,7 +153,7 @@ func ChooseRandomMapKey[M ~map[K]V, K comparable, V any](m M) (k K) { return } -// ChooseRandomMapValue 获取 map 中的随机 value +// ChooseRandomMapValue 获取 map 中的随机 inputV func ChooseRandomMapValue[M ~map[K]V, K comparable, V any](m M) (v V) { if m == nil { return @@ -164,14 +164,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 @@ -185,14 +185,14 @@ func ChooseRandomMapKeyN[M ~map[K]V, K comparable, V any](m M, n int) (result [] return } -// ChooseRandomMapValueN 获取 map 中的 n 个随机 value -// - 如果 n 大于 map 长度或小于 0 时将会发生 panic +// ChooseRandomMapValueN 获取 map 中的 inputN 个随机 inputV +// - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic func ChooseRandomMapValueN[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) i := 0 @@ -206,7 +206,7 @@ func ChooseRandomMapValueN[M ~map[K]V, K comparable, V any](m M, n int) (result return } -// ChooseRandomMapKeyAndValue 获取 map 中的随机 key 和 value +// ChooseRandomMapKeyAndValue 获取 map 中的随机 key 和 inputV func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V) { if m == nil { return @@ -217,14 +217,14 @@ func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V) return } -// ChooseRandomMapKeyAndValueN 获取 map 中的 n 个随机 key 和 value -// - 如果 n 大于 map 长度或小于 0 时将会发生 panic +// ChooseRandomMapKeyAndValueN 获取 map 中的 inputN 个随机 key 和 inputV +// - 如果 inputN 大于 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 diff --git a/utils/sher/sort.go b/utils/collection/sort.go similarity index 85% rename from utils/sher/sort.go rename to utils/collection/sort.go index 0ecc2e6..a8bb183 100644 --- a/utils/sher/sort.go +++ b/utils/collection/sort.go @@ -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 { diff --git a/utils/combination/matcher_options.go b/utils/combination/matcher_options.go index 2954147..26c1476 100644 --- a/utils/combination/matcher_options.go +++ b/utils/combination/matcher_options.go @@ -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 diff --git a/utils/compress/zip.go b/utils/compress/zip.go index b898dfd..3927ce0 100644 --- a/utils/compress/zip.go +++ b/utils/compress/zip.go @@ -41,7 +41,7 @@ func ZIPUnCompress(dataByte []byte) ([]byte, error) { if err != nil { return nil, err } - rc.Close() + _ = rc.Close() } return result.Bytes(), nil } diff --git a/utils/concurrent/slice.go b/utils/concurrent/slice.go index ac70601..0895c6c 100644 --- a/utils/concurrent/slice.go +++ b/utils/concurrent/slice.go @@ -1,7 +1,7 @@ package concurrent import ( - "github.com/kercylan98/minotaur/utils/slice" + "github.com/kercylan98/minotaur/utils/collection" "sync" ) @@ -55,5 +55,5 @@ func (slf *Slice[T]) Clear() { func (slf *Slice[T]) GetData() []T { slf.rw.Lock() defer slf.rw.Unlock() - return slice.Copy(slf.data) + return collection.CloneSlice(slf.data) } diff --git a/utils/counter/counter.go b/utils/counter/counter.go deleted file mode 100644 index 922a6db..0000000 --- a/utils/counter/counter.go +++ /dev/null @@ -1,214 +0,0 @@ -package counter - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/hash" - "sync" - "time" -) - -// NewCounter 创建一个计数器 -func NewCounter[K comparable, V generic.Number]() *Counter[K, V] { - c := &Counter[K, V]{ - c: make(map[K]any), - dr: make(map[K]int64), - } - return c -} - -// newSubCounter 创建一个子计数器 -func newSubCounter[K comparable, V generic.Number]() *Counter[K, V] { - c := &Counter[K, V]{ - c: make(map[K]any), - dr: make(map[K]int64), - sub: true, - } - return c -} - -// Counter 计数器 -type Counter[K comparable, V generic.Number] struct { - lock sync.RWMutex - sub bool - dr map[K]int64 - drm map[string]int64 - c map[K]any -} - -// AddWithMark 添加计数,根据 mark 判断是否重复计数 -func (slf *Counter[K, V]) AddWithMark(mark, key K, value V, expired time.Duration) { - slf.lock.Lock() - if expired > 0 { - now := time.Now().Unix() - mk := fmt.Sprintf("%v_%v", mark, key) - expiredTime, exist := slf.drm[mk] - if exist { - if expiredTime > now { - slf.lock.Unlock() - return - } - } - slf.drm[mk] = now + int64(expired/time.Second) - } - - v, exist := slf.c[key] - if !exist { - slf.c[key] = value - slf.lock.Unlock() - return - } - if v, ok := v.(V); !ok { - slf.lock.Unlock() - panic("counter value is sub counter") - } else { - slf.c[key] = v + value - } - slf.lock.Unlock() -} - -// Add 添加计数 -// - 当设置了 expired 时,在 expired 时间内,不会重复计数 -// - 需要注意的是,当第一次设置了 expired,第二次未设置时,第二次的计数将生效 -func (slf *Counter[K, V]) Add(key K, value V, expired ...time.Duration) { - slf.lock.Lock() - if len(expired) > 0 && expired[0] > 0 { - now := time.Now().Unix() - expiredTime, exist := slf.dr[key] - if exist { - if expiredTime > now { - slf.lock.Unlock() - return - } - } - slf.dr[key] = now + int64(expired[0]/time.Second) - } - - v, exist := slf.c[key] - if !exist { - slf.c[key] = value - slf.lock.Unlock() - return - } - if v, ok := v.(V); !ok { - slf.lock.Unlock() - panic("counter value is sub counter") - } else { - slf.c[key] = v + value - } - slf.lock.Unlock() -} - -// Get 获取计数 -func (slf *Counter[K, V]) Get(key K) V { - slf.lock.RLock() - v, exist := slf.c[key] - if !exist { - slf.lock.RUnlock() - return 0 - } - if v, ok := v.(V); !ok { - slf.lock.RUnlock() - panic("counter value is sub counter") - } else { - slf.lock.RUnlock() - return v - } -} - -// Reset 重置特定 key 的计数 -// - 当 key 为一个子计数器时,将会重置该子计数器 -func (slf *Counter[K, V]) Reset(key K) { - slf.lock.Lock() - delete(slf.c, key) - delete(slf.dr, key) - slf.lock.Unlock() -} - -// ResetMark 重置特定 mark 的 key 的计数 -func (slf *Counter[K, V]) ResetMark(mark, key K) { - slf.lock.Lock() - mk := fmt.Sprintf("%v_%v", mark, key) - delete(slf.c, key) - delete(slf.drm, mk) - slf.lock.Unlock() -} - -// ResetExpired 重置特定 key 的过期时间 -func (slf *Counter[K, V]) ResetExpired(key K) { - slf.lock.Lock() - delete(slf.dr, key) - slf.lock.Unlock() -} - -// ResetExpiredMark 重置特定 mark 的 key 的过期时间 -func (slf *Counter[K, V]) ResetExpiredMark(mark, key K) { - slf.lock.Lock() - mk := fmt.Sprintf("%v_%v", mark, key) - delete(slf.drm, mk) - slf.lock.Unlock() -} - -// ResetAll 重置所有计数 -func (slf *Counter[K, V]) ResetAll() { - slf.lock.Lock() - hash.Clear(slf.c) - hash.Clear(slf.dr) - hash.Clear(slf.drm) - slf.lock.Unlock() -} - -// SubCounter 获取子计数器 -func (slf *Counter[K, V]) SubCounter(key K) *Counter[K, V] { - slf.lock.Lock() - v, exist := slf.c[key] - if !exist { - counter := newSubCounter[K, V]() - slf.c[key] = counter - slf.lock.Unlock() - return counter - } - if v, ok := v.(*Counter[K, V]); !ok { - slf.lock.Unlock() - panic("counter value is count value") - } else { - slf.lock.Unlock() - return v - } -} - -// GetCounts 获取计数器的所有计数 -func (slf *Counter[K, V]) GetCounts() map[K]V { - counts := make(map[K]V) - slf.lock.RLock() - for k, v := range slf.c { - if v, ok := v.(V); !ok { - continue - } else { - counts[k] = v - } - } - slf.lock.RUnlock() - return counts -} - -// GetSubCounters 获取计数器的所有子计数器 -func (slf *Counter[K, V]) GetSubCounters() map[K]*Counter[K, V] { - counters := make(map[K]*Counter[K, V]) - slf.lock.RLock() - for k, v := range slf.c { - if v, ok := v.(*Counter[K, V]); !ok { - continue - } else { - counters[k] = v - } - } - slf.lock.RUnlock() - return counters -} - -// Shadow 获取该计数器的影子计数器,影子计数器的任何操作都不会影响到原计数器 -// - 适用于持久化等场景 -func (slf *Counter[K, V]) Shadow() *Shadow[K, V] { - return newShadow(slf) -} diff --git a/utils/counter/counter_test.go b/utils/counter/counter_test.go deleted file mode 100644 index 082c34e..0000000 --- a/utils/counter/counter_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package counter_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/counter" - "github.com/kercylan98/minotaur/utils/times" - "testing" - "time" -) - -func TestCounter_Add(t *testing.T) { - c := counter.NewCounter[string, int64]() - - c.Add("login_count", 1, times.GetNextDayInterval(time.Now())) - c.Add("login_count", 1, times.GetNextDayInterval(time.Now())) - c.SubCounter("live").Add("login_count", 1, times.GetNextDayInterval(time.Now())) - - fmt.Println(c) -} diff --git a/utils/counter/shadow.go b/utils/counter/shadow.go deleted file mode 100644 index 95ccd58..0000000 --- a/utils/counter/shadow.go +++ /dev/null @@ -1,57 +0,0 @@ -package counter - -import ( - "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/hash" -) - -func newShadow[K comparable, V generic.Number](counter *Counter[K, V]) *Shadow[K, V] { - counter.lock.RLock() - shadow := &Shadow[K, V]{ - Sub: counter.sub, - DeduplicationRecord: hash.Copy(counter.dr), - DeduplicationRecordMark: hash.Copy(counter.drm), - } - counter.lock.RUnlock() - shadow.Counter = counter.GetCounts() - subs := counter.GetSubCounters() - for k, c := range subs { - shadow.SubCounters[k] = newShadow(c) - } - return shadow -} - -// Shadow 计数器的影子计数器 -type Shadow[K comparable, V generic.Number] struct { - Sub bool // 是否为子计数器 - DeduplicationRecord map[K]int64 // 最后一次写入时间 - DeduplicationRecordMark map[string]int64 // 最后一次写入时间 - Counter map[K]V // 计数器 - SubCounters map[K]*Shadow[K, V] // 子计数器 -} - -// ToCounter 将影子计数器转换为计数器 -func (slf *Shadow[K, V]) ToCounter() *Counter[K, V] { - return slf.toCounter(nil) -} - -// toCounter 将影子计数器转换为计数器 -func (slf *Shadow[K, V]) toCounter(parent *Counter[K, V]) *Counter[K, V] { - counter := &Counter[K, V]{ - sub: slf.Sub, - c: map[K]any{}, - } - if slf.Sub { - counter.dr = parent.dr - } else { - counter.dr = hash.Copy(slf.DeduplicationRecord) - counter.drm = hash.Copy(slf.DeduplicationRecordMark) - } - for k, v := range slf.Counter { - counter.c[k] = v - } - for k, s := range slf.SubCounters { - counter.c[k] = s.toCounter(counter) - } - return counter -} diff --git a/utils/counter/simple_deduplication.go b/utils/counter/simple_deduplication.go deleted file mode 100644 index 8819737..0000000 --- a/utils/counter/simple_deduplication.go +++ /dev/null @@ -1,39 +0,0 @@ -package counter - -import ( - "github.com/kercylan98/minotaur/utils/generic" - "sync" -) - -// NewSimpleDeduplication 创建一个简单去重计数器 -// - 该计数器不会记录每个 key 的计数,只会记录 key 的存在与否 -// - 当 key 不存在时,计数器会将 key 记录为存在,并将计数器增加特定的值 -func NewSimpleDeduplication[K comparable, V generic.Number]() *SimpleDeduplication[K, V] { - return &SimpleDeduplication[K, V]{ - r: make(map[K]struct{}), - } -} - -// SimpleDeduplication 简单去重计数器 -type SimpleDeduplication[K comparable, V generic.Number] struct { - c V - r map[K]struct{} - l sync.RWMutex -} - -// Add 添加计数,根据 key 判断是否重复计数 -func (slf *SimpleDeduplication[K, V]) Add(key K, value V) { - slf.l.Lock() - defer slf.l.Unlock() - if _, exist := slf.r[key]; !exist { - slf.r[key] = struct{}{} - slf.c += value - } -} - -// Get 获取计数 -func (slf *SimpleDeduplication[K, V]) Get() V { - slf.l.RLock() - defer slf.l.RUnlock() - return slf.c -} diff --git a/utils/deck/deck.go b/utils/deck/deck.go index 8fda989..e9f1666 100644 --- a/utils/deck/deck.go +++ b/utils/deck/deck.go @@ -1,8 +1,7 @@ package deck import ( - "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/slice" + "github.com/kercylan98/minotaur/utils/collection" ) // NewDeck 创建一个新的甲板 @@ -21,7 +20,7 @@ type Deck[I Item] struct { // AddGroup 将一个组添加到甲板中 func (slf *Deck[I]) AddGroup(group *Group[I]) { - if !hash.Exist(slf.groups, group.GetGuid()) { + if !collection.FindInMapKey(slf.groups, group.GetGuid()) { slf.groups[group.GetGuid()] = group slf.sort = append(slf.sort, group.GetGuid()) } @@ -30,7 +29,7 @@ func (slf *Deck[I]) AddGroup(group *Group[I]) { // RemoveGroup 移除甲板中的一个组 func (slf *Deck[I]) RemoveGroup(guid int64) { delete(slf.groups, guid) - index := slice.GetIndex(slf.sort, guid) + index := collection.FindIndexInComparableSlice(slf.sort, guid) if index != -1 { slf.sort = append(slf.sort[:index], slf.sort[index+1:]...) } @@ -43,7 +42,7 @@ func (slf *Deck[I]) GetCount() int { // GetGroups 获取所有组 func (slf *Deck[I]) GetGroups() map[int64]*Group[I] { - return hash.Copy(slf.groups) + return collection.CloneMap(slf.groups) } // GetGroupsSlice 获取所有组 @@ -57,7 +56,7 @@ func (slf *Deck[I]) GetGroupsSlice() []*Group[I] { // GetNext 获取特定组的下一个组 func (slf *Deck[I]) GetNext(guid int64) *Group[I] { - index := slice.GetIndex(slf.sort, guid) + index := collection.FindIndexInComparableSlice(slf.sort, guid) if index == -1 { return nil } @@ -69,7 +68,7 @@ func (slf *Deck[I]) GetNext(guid int64) *Group[I] { // GetPrev 获取特定组的上一个组 func (slf *Deck[I]) GetPrev(guid int64) *Group[I] { - index := slice.GetIndex(slf.sort, guid) + index := collection.FindIndexInComparableSlice(slf.sort, guid) if index == -1 { return nil } diff --git a/utils/file/file.go b/utils/file/file.go index 81603f5..a579e95 100644 --- a/utils/file/file.go +++ b/utils/file/file.go @@ -2,7 +2,7 @@ package file import ( "bufio" - "github.com/kercylan98/minotaur/utils/slice" + "github.com/kercylan98/minotaur/utils/collection" "io" "os" "path/filepath" @@ -150,12 +150,12 @@ func Paths(dir string) []string { return paths } -// ReadLineWithParallelByChannel 并行的分行读取文件并行处理,处理过程中会将每一行的内容传入 handlerFunc 中进行处理 +// ReadLineWithParallel 并行的分行读取文件并行处理,处理过程中会将每一行的内容传入 handlerFunc 中进行处理 // - 由于是并行处理,所以处理过程中的顺序是不确定的。 // - 可通过 start 参数指定开始读取的位置,如果不指定则从文件开头开始读取。 func ReadLineWithParallel(filename string, chunkSize int64, handlerFunc func(string), start ...int64) (n int64, err error) { file, err := os.Open(filename) - offset := slice.GetValue(start, 0) + offset := collection.FindFirstOrDefaultInSlice(start, 0) if err != nil { return offset, err } diff --git a/utils/fsm/fsm.go b/utils/fsm/fsm.go index 2bc462e..283e1fb 100644 --- a/utils/fsm/fsm.go +++ b/utils/fsm/fsm.go @@ -2,7 +2,7 @@ package fsm import ( "fmt" - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/collection" ) // NewFSM 创建一个新的状态机 @@ -60,7 +60,7 @@ func (slf *FSM[State, Data]) Unregister(state State) { // HasState 检查状态机是否存在特定状态 func (slf *FSM[State, Data]) HasState(state State) bool { - return hash.Exist(slf.states, state) + return collection.FindInMapKey(slf.states, state) } // Change 改变状态机状态到新的状态 diff --git a/utils/geometry/shape.go b/utils/geometry/shape.go index 5ee2b57..97e3271 100644 --- a/utils/geometry/shape.go +++ b/utils/geometry/shape.go @@ -3,8 +3,8 @@ package geometry import ( "bytes" "fmt" + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/slice" "math" "sort" "strings" @@ -317,7 +317,7 @@ func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result [] for i, directions := range [][]Direction{DirectionUDLR, DirectionLRUD} { var direction Direction for { - next, direction = slice.NextLoop(directions, next) + next, direction = collection.FindLoopedNextInSlice(directions, next) for { directionPoint = GetDirectionNextWithPoint(direction, directionPoint) if px, py := directionPoint.GetXY(); px < 0 || px >= areaWidth || py < 0 || py >= areaHeight { diff --git a/utils/hash/chunk.go b/utils/hash/chunk.go deleted file mode 100644 index 0f690c8..0000000 --- a/utils/hash/chunk.go +++ /dev/null @@ -1,27 +0,0 @@ -package hash - -// Chunk 将哈希表按照指定大小分块 -// - m: 待分块的哈希表 -// - size: 每块的大小 -func Chunk[K comparable, V any](m map[K]V, size int) []map[K]V { - if len(m) == 0 { - return nil - } - - var ( - i int - j int - ) - chunks := make([]map[K]V, (len(m)-1)/size+1) - for i = 0; i < len(m); i += size { - chunks[j] = make(map[K]V, size) - for key, value := range m { - if i <= j*size && j*size < i+size { - chunks[j][key] = value - } - } - j++ - } - - return chunks -} diff --git a/utils/hash/consistency.go b/utils/hash/consistency.go deleted file mode 100644 index 6c2f310..0000000 --- a/utils/hash/consistency.go +++ /dev/null @@ -1,77 +0,0 @@ -package hash - -import ( - "fmt" - "hash/crc32" - "sort" - "strconv" - "strings" -) - -func NewConsistency(replicas int) *Consistency { - return &Consistency{ - replicas: replicas, - } -} - -// Consistency 一致性哈希生成 -// -// https://blog.csdn.net/zhpCSDN921011/article/details/126845397 -type Consistency struct { - replicas int // 虚拟节点的数量 - keys []int // 所有虚拟节点的哈希值 - hashMap map[int]int // 虚拟节点的哈希值: 节点(虚拟节点映射到真实节点) -} - -// AddNode 添加节点 -func (slf *Consistency) AddNode(keys ...int) { - if slf.hashMap == nil { - slf.hashMap = map[int]int{} - } - if slf.replicas == 0 { - slf.replicas = 3 - } - for _, key := range keys { - for i := 0; i < slf.replicas; i++ { - // 计算虚拟节点哈希值 - hash := int(crc32.ChecksumIEEE([]byte(strconv.Itoa(i) + strconv.Itoa(key)))) - - // 存储虚拟节点哈希值 - slf.keys = append(slf.keys, hash) - - // 存入map做映射 - slf.hashMap[hash] = key - } - } - - //排序哈希值,下面匹配的时候要二分搜索 - sort.Ints(slf.keys) -} - -// PickNode 获取与 key 最接近的节点 -func (slf *Consistency) PickNode(key any) int { - if len(slf.keys) == 0 { - return 0 - } - - partitionKey := fmt.Sprintf("%#v", key) - beg := strings.Index(partitionKey, "{") - end := strings.Index(partitionKey, "}") - if beg != -1 && !(end == -1 || end == beg+1) { - partitionKey = partitionKey[beg+1 : end] - } - - // 计算传入key的哈希值 - hash := int(crc32.ChecksumIEEE([]byte(partitionKey))) - - // sort.Search使用二分查找满足m.Keys[i]>=hash的最小哈希值 - idx := sort.Search(len(slf.keys), func(i int) bool { return slf.keys[i] >= hash }) - - // 若key的hash值大于最后一个虚拟节点的hash值,则选择第一个虚拟节点 - if idx == len(slf.keys) { - idx = 0 - } - - return slf.hashMap[slf.keys[idx]] - -} diff --git a/utils/hash/convert.go b/utils/hash/convert.go deleted file mode 100644 index 35113ff..0000000 --- a/utils/hash/convert.go +++ /dev/null @@ -1,73 +0,0 @@ -package hash - -// ToSlice 将 map 的 value 转换为切片 -func ToSlice[K comparable, V any](m map[K]V) []V { - var s = make([]V, 0, len(m)) - for _, v := range m { - s = append(s, v) - } - return s -} - -// KeyToSlice 将 map 的 key 转换为切片 -func KeyToSlice[K comparable, V any](m map[K]V) []K { - var s = make([]K, 0, len(m)) - for k := range m { - s = append(s, k) - } - return s -} - -// Reversal 将 map 的 key 和 value 互换 -func Reversal[K comparable, V comparable](m map[K]V) map[V]K { - var nm = make(map[V]K) - for k, v := range m { - nm[v] = k - } - return nm -} - -// ToMap 将切片转换为 map -func ToMap[V any](slice []V) map[int]V { - var m = make(map[int]V) - for i, v := range slice { - m[i] = v - } - return m -} - -// ToIterator 将切片转换为 Iterator -func ToIterator[V comparable](slice []V) map[V]struct{} { - var m = make(map[V]struct{}) - for _, v := range slice { - m[v] = struct{}{} - } - return m -} - -// ToMapBool 将切片转换为 map,value作为Key -func ToMapBool[V comparable](slice []V) map[V]bool { - var m = make(map[V]bool) - for _, v := range slice { - m[v] = true - } - return m -} - -// ToSortMap 将切片转换为 SortMap -func ToSortMap[V any](slice []V) SortMap[int, V] { - var m SortMap[int, V] - for i, v := range slice { - m.Set(i, v) - } - return m -} - -// Copy 复制一个map -func Copy[K comparable, V any](m map[K]V) map[K]V { - var backup = make(map[K]V) - for k, v := range m { - backup[k] = v - } - return backup -} diff --git a/utils/hash/drop.go b/utils/hash/drop.go deleted file mode 100644 index 050f712..0000000 --- a/utils/hash/drop.go +++ /dev/null @@ -1,56 +0,0 @@ -package hash - -// RandomDrop 随机删除哈希表中的指定数量的元素 -// - 该函数会修改原哈希表,如果不想修改原哈希表,请使用 RandomDropCopy -func RandomDrop[K comparable, V any](n int, hash map[K]V) map[K]V { - if n <= 0 || len(hash) <= n { - return hash - } - for k := range hash { - if n <= 0 { - break - } - delete(hash, k) - n-- - } - return hash -} - -// RandomDropCopy 随机删除哈希表中的指定数量的元素 -// - 该函数不会修改原哈希表,如果想修改原哈希表,请使用 RandomDrop -func RandomDropCopy[K comparable, V any](n int, m map[K]V) map[K]V { - if n <= 0 || len(m) <= n { - return map[K]V{} - } - var nm = make(map[K]V, len(m)) - for k, v := range m { - if n <= 0 { - break - } - nm[k] = v - n-- - } - return nm -} - -// DropBy 从哈希表中删除指定的元素 -func DropBy[K comparable, V any](m map[K]V, fn func(key K, value V) bool) map[K]V { - for k, v := range m { - if fn(k, v) { - delete(m, k) - } - } - return m -} - -// DropByCopy 与 DropBy 功能相同,但是该函数不会修改原哈希表 -func DropByCopy[K comparable, V any](m map[K]V, fn func(key K, value V) bool) map[K]V { - var nm = make(map[K]V, len(m)) - for k, v := range m { - if fn(k, v) { - continue - } - nm[k] = v - } - return nm -} diff --git a/utils/hash/each.go b/utils/hash/each.go deleted file mode 100644 index 85324b4..0000000 --- a/utils/hash/each.go +++ /dev/null @@ -1,47 +0,0 @@ -package hash - -// Each 根据传入的 abort 遍历 m,如果 iterator 返回值与 abort 相同,则停止遍历 -func Each[K comparable, V any](abort bool, m map[K]V, iterator func(i int, key K, item V) bool) { - i := 0 - for k, v := range m { - if iterator(i, k, v) == abort { - break - } - i++ - } -} - -// EachT 与 Each 的功能相同,但是 abort 被默认为 true -func EachT[K comparable, V any](m map[K]V, iterator func(i int, key K, item V) bool) { - Each(true, m, iterator) -} - -// EachF 与 Each 的功能相同,但是 abort 被默认为 false -func EachF[K comparable, V any](m map[K]V, iterator func(i int, key K, item V) bool) { - Each(false, m, iterator) -} - -// EachResult 根据传入的 abort 遍历 m,得到遍历的结果,如果 iterator 返回值中的 bool 值与 abort 相同,则停止遍历,并返回当前已积累的结果 -func EachResult[K comparable, V any, R any](abort bool, m map[K]V, iterator func(i int, key K, item V) (R, bool)) []R { - var result []R - i := 0 - for k, v := range m { - r, ok := iterator(i, k, v) - result = append(result, r) - if ok == abort { - break - } - i++ - } - return result -} - -// EachResultT 与 EachResult 的功能相同,但是 abort 被默认为 true -func EachResultT[K comparable, V any, R any](m map[K]V, iterator func(i int, key K, item V) (R, bool)) []R { - return EachResult(true, m, iterator) -} - -// EachResultF 与 EachResult 的功能相同,但是 abort 被默认为 false -func EachResultF[K comparable, V any, R any](m map[K]V, iterator func(i int, key K, item V) (R, bool)) []R { - return EachResult(false, m, iterator) -} diff --git a/utils/hash/filter.go b/utils/hash/filter.go deleted file mode 100644 index 50bed1d..0000000 --- a/utils/hash/filter.go +++ /dev/null @@ -1,56 +0,0 @@ -package hash - -// Filter 根据特定的表达式过滤哈希表成员 -// - reserve: 是否保留符合条件的成员 -// - m: 待过滤的哈希表 -// - expression: 过滤表达式 -// -// 这个函数的作用是遍历输入哈希表 m,然后根据 expression 函数的返回值来决定是否保留每个元素。具体来说 -// - 如果 expression 返回 true 并且 reserve 也是 true,那么元素会被保留 -// - 如果 expression 返回 false 并且 reserve 是 false,那么元素也会被保留 -// -// 该没有创建新的内存空间或进行元素复制,所以整个操作相当高效。同时,由于 m 和 map 实际上共享底层的数组,因此这个函数会改变传入的 map。如果不希望改变原哈希表,需要在函数调用之前手动复制一份或者使用 FilterCopy 函数。 -func Filter[K comparable, V any](reserve bool, m map[K]V, expression func(key K, value V) bool) map[K]V { - if len(m) == 0 { - return m - } - - for key, value := range m { - if !expression(key, value) { - delete(m, key) - } - } - - return m -} - -// FilterT 与 Filter 的功能相同,但是 reserve 被默认为 true -func FilterT[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { - return Filter(true, m, expression) -} - -// FilterF 与 Filter 的功能相同,但是 reserve 被默认为 false -func FilterF[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { - return Filter(false, m, expression) -} - -// FilterCopy 与 Filter 的功能相同,但是不会改变原哈希表,而是返回一个新的哈希表 -func FilterCopy[K comparable, V any](reserve bool, m map[K]V, expression func(key K, value V) bool) map[K]V { - var res = map[K]V{} - for key, value := range m { - if expression(key, value) { - res[key] = value - } - } - return res -} - -// FilterTCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 true -func FilterTCopy[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { - return FilterCopy(true, m, expression) -} - -// FilterFCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 false -func FilterFCopy[K comparable, V any](m map[K]V, expression func(key K, value V) bool) map[K]V { - return FilterCopy(false, m, expression) -} diff --git a/utils/hash/hash.go b/utils/hash/hash.go deleted file mode 100644 index 60ec9bb..0000000 --- a/utils/hash/hash.go +++ /dev/null @@ -1,74 +0,0 @@ -package hash - -import "encoding/json" - -// Exist 检查特定 key 是否存在 -func Exist[K comparable, V any](m map[K]V, key K) bool { - _, exist := m[key] - return exist -} - -// AllExist 检查多个 key 是否存在 -func AllExist[K comparable, V any](m map[K]V, keys ...K) bool { - for key := range m { - if _, exist := m[key]; !exist { - return false - } - } - return true -} - -// ToJson 将 map 转换为 json 字符串 -func ToJson[K comparable, V any](m map[K]V) string { - if data, err := json.Marshal(m); err == nil { - return string(data) - } - return "{}" -} - -// RandomGet 随机获取一个元素 -func RandomGet[K comparable, V any](m map[K]V) (v V) { - for _, v := range m { - return v - } - return -} - -// RandomGetKey 随机获取一个 key -func RandomGetKey[K comparable, V any](m map[K]V) (k K) { - for k = range m { - return k - } - return -} - -// RandomGetN 随机获取 n 个元素 -// - 获取到的元素不会是重复的,当 map 的长度不足 n 时,返回的元素等同于 hash.ToSlice -func RandomGetN[K comparable, V any](m map[K]V, n int) (vs []V) { - for _, v := range m { - vs = append(vs, v) - if len(vs) >= n { - return - } - } - return -} - -// RandomGetKeyN 随机获取 n 个 key -// - 获取到的元素不会是重复的,当 map 的长度不足 n 时,返回的元素等同于 hash.KeyToSlice -func RandomGetKeyN[K comparable, V any](m map[K]V, n int) (ks []K) { - for k := range m { - ks = append(ks, k) - if len(ks) >= n { - return - } - } - return -} - -// Clear 清空 map -func Clear[K comparable, V any](m map[K]V) { - for k := range m { - delete(m, k) - } -} diff --git a/utils/hash/math.go b/utils/hash/math.go deleted file mode 100644 index 1c71ef6..0000000 --- a/utils/hash/math.go +++ /dev/null @@ -1,12 +0,0 @@ -package hash - -import "github.com/kercylan98/minotaur/utils/generic" - -// Sum 计算一个 map 中的 value 总和 -func Sum[K comparable, V generic.Number](m map[K]V) V { - var sum V - for _, v := range m { - sum += v - } - return sum -} diff --git a/utils/hash/set.go b/utils/hash/set.go deleted file mode 100644 index 76477cf..0000000 --- a/utils/hash/set.go +++ /dev/null @@ -1,90 +0,0 @@ -package hash - -// NewSet 创建一个 Set 集合 -func NewSet[K comparable](ks ...K) Set[K] { - s := make(Set[K]) - s.AddAll(ks...) - return s -} - -// Set 集合 -type Set[K comparable] map[K]struct{} - -// Exist 检查特定 key 是否存在 -func (s Set[K]) Exist(key K) bool { - _, exist := s[key] - return exist -} - -// AllExist 检查多个 key 是否存在 -func (s Set[K]) AllExist(keys ...K) bool { - for _, key := range keys { - if _, exist := s[key]; !exist { - return false - } - } - return true -} - -// Add 添加元素 -func (s Set[K]) Add(key K) { - s[key] = struct{}{} -} - -// AddAll 添加多个元素 -func (s Set[K]) AddAll(keys ...K) { - for _, key := range keys { - s[key] = struct{}{} - } -} - -// Remove 移除元素 -func (s Set[K]) Remove(key K) { - delete(s, key) -} - -// RemoveAll 移除多个元素 -func (s Set[K]) RemoveAll(keys ...K) { - for _, key := range keys { - delete(s, key) - } -} - -// Clear 清空集合 -func (s Set[K]) Clear() { - for key := range s { - delete(s, key) - } -} - -// Size 集合长度 -func (s Set[K]) Size() int { - return len(s) -} - -// ToSlice 转换为切片 -func (s Set[K]) ToSlice() []K { - keys := make([]K, 0, len(s)) - for key := range s { - keys = append(keys, key) - } - return keys -} - -// ToMap 转换为 map -func (s Set[K]) ToMap() map[K]struct{} { - return s -} - -// ToJson 转换为 json 字符串 -func (s Set[K]) ToJson() string { - return ToJson(s) -} - -// RandomGet 随机获取一个元素 -func (s Set[K]) RandomGet() (k K) { - for k = range s { - return k - } - return -} diff --git a/utils/hash/srot_map.go b/utils/hash/srot_map.go deleted file mode 100644 index a913413..0000000 --- a/utils/hash/srot_map.go +++ /dev/null @@ -1,106 +0,0 @@ -package hash - -import ( - "sort" -) - -func NewSortMap[K comparable, V any]() *SortMap[K, V] { - return &SortMap[K, V]{ - m: map[K]V{}, - s: map[int]K{}, - r: map[K]int{}, - } -} - -// SortMap 有序的 map 实现 -type SortMap[K comparable, V any] struct { - i int - m map[K]V - s map[int]K - r map[K]int -} - -func (slf *SortMap[K, V]) Set(key K, value V) { - if i, exist := slf.r[key]; exist { - slf.s[i] = key - slf.m[key] = value - } else { - slf.m[key] = value - slf.s[slf.i] = key - slf.r[key] = slf.i - slf.i++ - } -} - -func (slf *SortMap[K, V]) Del(key K) { - if _, exist := slf.m[key]; exist { - delete(slf.s, slf.r[key]) - delete(slf.r, key) - delete(slf.m, key) - } -} - -func (slf *SortMap[K, V]) Get(key K) V { - v := slf.m[key] - return v -} - -func (slf *SortMap[K, V]) For(handle func(key K, value V) bool) { - for k, v := range slf.m { - if !handle(k, v) { - break - } - } -} - -func (slf *SortMap[K, V]) ForSort(handle func(key K, value V) bool) { - var indexes []int - for i := range slf.s { - indexes = append(indexes, i) - } - sort.Ints(indexes) - for _, i := range indexes { - k := slf.s[i] - if !handle(k, slf.m[k]) { - break - } - } -} - -func (slf *SortMap[K, V]) ToMap() map[K]V { - var m = make(map[K]V) - for k, v := range slf.m { - m[k] = v - } - return m -} - -func (slf *SortMap[K, V]) ToSlice() []V { - var s = make([]V, 0, len(slf.m)) - for _, v := range slf.m { - s = append(s, v) - } - return s -} - -func (slf *SortMap[K, V]) ToSliceSort() []V { - var indexes []int - for i := range slf.s { - indexes = append(indexes, i) - } - sort.Ints(indexes) - var result []V - for _, i := range indexes { - k := slf.s[i] - result = append(result, slf.m[k]) - } - return result -} - -func (slf *SortMap[K, V]) KeyToSlice() []K { - var s = make([]K, 0, len(slf.m)) - for k := range slf.m { - s = append(s, k) - } - return s -} diff --git a/utils/maths/compare.go b/utils/maths/compare.go index 12dd246..70eb450 100644 --- a/utils/maths/compare.go +++ b/utils/maths/compare.go @@ -1,8 +1,8 @@ package maths import ( + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/slice" "sort" ) @@ -35,7 +35,7 @@ func Compare[V generic.Ordered](a V, expression CompareExpression, b V) bool { } // IsContinuity 检查一组值是否连续 -func IsContinuity[V generic.Integer](values []V) bool { +func IsContinuity[S ~[]V, V generic.Integer](values S) bool { length := len(values) if length == 0 { return false @@ -52,10 +52,10 @@ func IsContinuity[V generic.Integer](values []V) bool { } // IsContinuityWithSort 检查一组值排序后是否连续 -func IsContinuityWithSort[V generic.Integer](values []V) bool { - sli := slice.Copy(values) - sort.Slice(sli, func(i, j int) bool { - return sli[i] < sli[j] +func IsContinuityWithSort[S ~[]V, V generic.Integer](values S) bool { + s := collection.CloneSlice(values) + sort.Slice(s, func(i, j int) bool { + return s[i] < s[j] }) - return IsContinuity(sli) + return IsContinuity(s) } diff --git a/utils/maths/math.go b/utils/maths/math.go index 6166c87..7cb7d9f 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -140,7 +140,7 @@ func UnMergeInt64[V generic.SignedNumber](n int64) (V, V) { // ToContinuous 将一组非连续的数字转换为从1开始的连续数字 // - 返回值是一个 map,key 是从 1 开始的连续数字,value 是原始数字 -func ToContinuous[V generic.Integer](nums []V) map[V]V { +func ToContinuous[S ~[]V, V generic.Integer](nums S) map[V]V { if len(nums) == 0 { return nil } diff --git a/utils/memory/memory.go b/utils/memory/memory.go index d9e407c..a8bfa21 100644 --- a/utils/memory/memory.go +++ b/utils/memory/memory.go @@ -2,7 +2,7 @@ package memory import ( "fmt" - "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/super" "reflect" @@ -81,7 +81,7 @@ func BindPersistCacheProgram[OutputParamHandlerFunc any](name string, handler Ou cachesRWMutex.RUnlock() return } - funcCache = hash.Copy(funcCache) + funcCache = collection.CloneMap(funcCache) cachesRWMutex.RUnlock() for _, results := range funcCache { persist.Call(results) @@ -111,7 +111,7 @@ func BindPersistCacheProgram[OutputParamHandlerFunc any](name string, handler Ou cachesRWMutex.RUnlock() return } - funcCache = hash.Copy(funcCache) + funcCache = collection.CloneMap(funcCache) cachesRWMutex.RUnlock() delay := opt.delay tick := delay diff --git a/utils/sher/contains.go b/utils/sher/contains.go deleted file mode 100644 index 35aa391..0000000 --- a/utils/sher/contains.go +++ /dev/null @@ -1,308 +0,0 @@ -package sher - -import "sort" - -type ComparisonHandler[V any] func(source, target V) bool - -// InSlice 判断切片中是否包含某个元素 -func InSlice[S ~[]V, V any](slice S, v V, handler ComparisonHandler[V]) bool { - if slice == nil { - return false - } - for _, value := range slice { - if handler(v, value) { - return true - } - } - return false -} - -// InSliceByBinarySearch 判断切片中是否包含某个元素,使用二分搜索 -func InSliceByBinarySearch[S ~[]V, V any](slice S, v V, handler ComparisonHandler[V]) bool { - return sort.Search(len(slice), func(i int) bool { - return handler(v, slice[i]) - }) != len(slice) -} - -// AllInSlice 判断切片中是否包含所有元素 -func AllInSlice[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool { - if slice == nil { - return false - } - for _, value := range values { - if !InSlice(slice, value, handler) { - return false - } - } - return true -} - -// AllInSliceByBinarySearch 判断切片中是否包含所有元素,使用二分搜索 -func AllInSliceByBinarySearch[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool { - if slice == nil { - return false - } - for _, value := range values { - if !InSliceByBinarySearch(slice, value, handler) { - return false - } - } - return true -} - -// AnyInSlice 判断切片中是否包含任意一个元素 -func AnyInSlice[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool { - if slice == nil { - return false - } - for _, value := range values { - if InSlice(slice, value, handler) { - return true - } - } - return false -} - -// AnyInSliceByBinarySearch 判断切片中是否包含任意一个元素,使用二分搜索 -func AnyInSliceByBinarySearch[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool { - if slice == nil { - return false - } - for _, value := range values { - if InSliceByBinarySearch(slice, value, handler) { - return true - } - } - return false -} - -// InSlices 判断多个切片中是否包含某个元素 -func InSlices[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool { - return InSlice(MergeSlices(slices...), v, handler) -} - -// InSlicesByBinarySearch 判断多个切片中是否包含某个元素,使用二分搜索 -func InSlicesByBinarySearch[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool { - return InSliceByBinarySearch(MergeSlices(slices...), v, handler) -} - -// AllInSlices 判断多个切片中是否包含所有元素 -func AllInSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool { - return AllInSlice(MergeSlices(slices...), values, handler) -} - -// AllInSlicesByBinarySearch 判断多个切片中是否包含所有元素,使用二分搜索 -func AllInSlicesByBinarySearch[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool { - return AllInSliceByBinarySearch(MergeSlices(slices...), values, handler) -} - -// AnyInSlices 判断多个切片中是否包含任意一个元素 -func AnyInSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool { - return AnyInSlice(MergeSlices(slices...), values, handler) -} - -// AnyInSlicesByBinarySearch 判断多个切片中是否包含任意一个元素,使用二分搜索 -func AnyInSlicesByBinarySearch[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool { - return AnyInSliceByBinarySearch(MergeSlices(slices...), values, handler) -} - -// InAllSlices 判断元素是否在所有切片中都存在 -func InAllSlices[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool { - if slices == nil { - return false - } - for _, slice := range slices { - if !InSlice(slice, v, handler) { - return false - } - } - return true -} - -// InAllSlicesByBinarySearch 判断元素是否在所有切片中都存在,使用二分搜索 -func InAllSlicesByBinarySearch[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool { - if slices == nil { - return false - } - for _, slice := range slices { - if !InSliceByBinarySearch(slice, v, handler) { - return false - } - } - return true -} - -// AnyInAllSlices 判断元素是否在所有切片中都存在任意至少一个 -func AnyInAllSlices[S ~[]V, V any](slices []S, v []V, handler ComparisonHandler[V]) bool { - if slices == nil { - return false - } - for _, slice := range slices { - if AnyInSlice(slice, v, handler) { - return true - } - } - return false -} - -// AnyInAllSlicesByBinarySearch 判断元素是否在所有切片中都存在任意至少一个,使用二分搜索 -func AnyInAllSlicesByBinarySearch[S ~[]V, V any](slices []S, v []V, handler ComparisonHandler[V]) bool { - if slices == nil { - return false - } - for _, slice := range slices { - if AnyInSliceByBinarySearch(slice, v, handler) { - return true - } - } - return false -} - -// KeyInMap 判断 map 中是否包含某个 key -func KeyInMap[M ~map[K]V, K comparable, V any](m M, key K) bool { - _, ok := m[key] - return ok -} - -// ValueInMap 判断 map 中是否包含某个 value -func ValueInMap[M ~map[K]V, K comparable, V any](m M, value V, handler ComparisonHandler[V]) bool { - if m == nil { - return false - } - for _, v := range m { - if handler(value, v) { - return true - } - } - return false -} - -// AllKeyInMap 判断 map 中是否包含所有 key -func AllKeyInMap[M ~map[K]V, K comparable, V any](m M, keys ...K) bool { - if m == nil { - return false - } - for _, key := range keys { - if !KeyInMap(m, key) { - return false - } - } - return true -} - -// AllValueInMap 判断 map 中是否包含所有 value -func AllValueInMap[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) bool { - if m == nil { - return false - } - for _, value := range values { - if !ValueInMap(m, value, handler) { - return false - } - } - return true -} - -// AnyKeyInMap 判断 map 中是否包含任意一个 key -func AnyKeyInMap[M ~map[K]V, K comparable, V any](m M, keys ...K) bool { - if m == nil { - return false - } - for _, key := range keys { - if KeyInMap(m, key) { - return true - } - } - return false -} - -// AnyValueInMap 判断 map 中是否包含任意一个 value -func AnyValueInMap[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) bool { - if m == nil { - return false - } - for _, value := range values { - if ValueInMap(m, value, handler) { - return true - } - } - return false -} - -// AllKeyInMaps 判断多个 map 中是否包含所有 key -func AllKeyInMaps[M ~map[K]V, K comparable, V any](maps []M, keys ...K) bool { - if maps == nil { - return false - } - for _, m := range maps { - if !AllKeyInMap(m, keys...) { - return false - } - } - return true -} - -// AllValueInMaps 判断多个 map 中是否包含所有 value -func AllValueInMaps[M ~map[K]V, K comparable, V any](maps []M, values []V, handler ComparisonHandler[V]) bool { - if maps == nil { - return false - } - for _, m := range maps { - if !AllValueInMap(m, values, handler) { - return false - } - } - return true -} - -// AnyKeyInMaps 判断多个 map 中是否包含任意一个 key -func AnyKeyInMaps[M ~map[K]V, K comparable, V any](maps []M, keys ...K) bool { - if maps == nil { - return false - } - for _, m := range maps { - if AnyKeyInMap(m, keys...) { - return true - } - } - return false -} - -// AnyValueInMaps 判断多个 map 中是否包含任意一个 value -func AnyValueInMaps[M ~map[K]V, K comparable, V any](maps []M, values []V, handler ComparisonHandler[V]) bool { - if maps == nil { - return false - } - for _, m := range maps { - if AnyValueInMap(m, values, handler) { - return true - } - } - return false -} - -// InAllMaps 判断元素是否在所有 map 中都存在 -func InAllMaps[M ~map[K]V, K comparable, V any](maps []M, key K) bool { - if maps == nil { - return false - } - for _, m := range maps { - if !KeyInMap(m, key) { - return false - } - } - return true -} - -// AnyInAllMaps 判断元素是否在所有 map 中都存在任意至少一个 -func AnyInAllMaps[M ~map[K]V, K comparable, V any](maps []M, keys []K) bool { - if maps == nil { - return false - } - for _, m := range maps { - if AnyKeyInMap(m, keys...) { - return true - } - } - return false -} diff --git a/utils/sher/doc.go b/utils/sher/doc.go deleted file mode 100644 index 90d3702..0000000 --- a/utils/sher/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package sher 用于对 slice 和 map 操作的工具函数,将逐步替代 utils/slice 和 utils/map -package sher diff --git a/utils/sher/duplicate_test.go b/utils/sher/duplicate_test.go deleted file mode 100644 index 6498e20..0000000 --- a/utils/sher/duplicate_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package sher_test - -import ( - "github.com/kercylan98/minotaur/utils/sher" - "testing" -) - -func TestDeduplicateSliceInPlace(t *testing.T) { - var cases = []struct { - s []int - expected []int - }{ - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - } - - for _, c := range cases { - sher.DeduplicateSliceInPlace(&c.s) - if len(c.s) != len(c.expected) { - t.Errorf("DeduplicateSliceInPlace(%v) == %v, expected %v", c.s, c.s, c.expected) - } - } -} - -func TestDeduplicateSlice(t *testing.T) { - var cases = []struct { - s []int - expected []int - }{ - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - } - - for _, c := range cases { - sl := len(c.s) - if r := sher.DeduplicateSlice(c.s); len(r) != len(c.expected) || len(c.s) != sl { - t.Errorf("DeduplicateSlice(%v) == %v, expected %v", c.s, r, c.expected) - } - } -} - -func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) { - var cases = []struct { - s []int - expected []int - }{ - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - } - - for _, c := range cases { - sher.DeduplicateSliceInPlaceWithCompare(&c.s, func(a, b int) bool { - return a == b - }) - if len(c.s) != len(c.expected) { - t.Errorf("DeduplicateSliceInPlaceWithCompare(%v) == %v, expected %v", c.s, c.s, c.expected) - } - } -} - -func TestDeduplicateSliceWithCompare(t *testing.T) { - var cases = []struct { - s []int - expected []int - }{ - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - { - s: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2}, - expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, - }, - } - - for _, c := range cases { - sl := len(c.s) - if r := sher.DeduplicateSliceWithCompare(c.s, func(a, b int) bool { - return a == b - }); len(r) != len(c.expected) || len(c.s) != sl { - t.Errorf("DeduplicateSliceWithCompare(%v) == %v, expected %v", c.s, r, c.expected) - } - } -} diff --git a/utils/sher/find.go b/utils/sher/find.go deleted file mode 100644 index 9dad6a1..0000000 --- a/utils/sher/find.go +++ /dev/null @@ -1,122 +0,0 @@ -package sher - -import "github.com/kercylan98/minotaur/utils/generic" - -// FindInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 -func FindInSlice[S ~[]V, V any](slice S, handler func(v V) bool) (i int, t V) { - if slice == nil { - return -1, t - } - for i, v := range slice { - if handler(v) { - return i, v - } - } - return -1, t -} - -// FindInSliceByBinary 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 -func FindInSliceByBinary[S ~[]V, V any](slice S, handler func(v V) bool) (i int, t V) { - low := 0 - high := len(slice) - 1 - - for low <= high { - mid := low + (high-low)/2 - if handler(slice[mid]) { - return mid, slice[mid] - } else if handler(slice[mid]) { - high = mid - 1 - } else { - low = mid + 1 - } - } - return -1, t -} - -// FindMinimumInSlice 获取切片中的最小值 -func FindMinimumInSlice[S ~[]V, V generic.Number](slice S, handler ComparisonHandler[V]) (result V) { - if slice == nil { - return - } - result = slice[0] - for i := 1; i < len(slice); i++ { - if handler(slice[i], result) { - result = slice[i] - } - } - return -} - -// FindMaximumInSlice 获取切片中的最大值 -func FindMaximumInSlice[S ~[]V, V generic.Number](slice S, handler ComparisonHandler[V]) (result V) { - if slice == nil { - return - } - result = slice[0] - for i := 1; i < len(slice); i++ { - if handler(result, slice[i]) { - result = slice[i] - } - } - return -} - -// FindMin2MaxInSlice 获取切片中的最小值和最大值 -func FindMin2MaxInSlice[S ~[]V, V generic.Number](slice S, handler ComparisonHandler[V]) (min, max V) { - if slice == nil { - return - } - min = slice[0] - max = slice[0] - for i := 1; i < len(slice); i++ { - if handler(slice[i], min) { - min = slice[i] - } - if handler(max, slice[i]) { - max = slice[i] - } - } - return -} - -// FindMinFromMap 获取 map 中的最小值 -func FindMinFromMap[M ~map[K]V, K comparable, V generic.Number](m M, handler ComparisonHandler[V]) (result V) { - if m == nil { - return - } - for _, v := range m { - if handler(v, result) { - result = v - } - } - return -} - -// FindMaxFromMap 获取 map 中的最大值 -func FindMaxFromMap[M ~map[K]V, K comparable, V generic.Number](m M, handler ComparisonHandler[V]) (result V) { - if m == nil { - return - } - for _, v := range m { - if handler(result, v) { - result = v - } - } - return -} - -// FindMin2MaxFromMap 获取 map 中的最小值和最大值 -func FindMin2MaxFromMap[M ~map[K]V, K comparable, V generic.Number](m M, handler ComparisonHandler[V]) (min, max V) { - if m == nil { - return - } - for _, v := range m { - if handler(v, min) { - min = v - } - if handler(max, v) { - max = v - } - } - return -} diff --git a/utils/slice/chunk.go b/utils/slice/chunk.go deleted file mode 100644 index 311d2f5..0000000 --- a/utils/slice/chunk.go +++ /dev/null @@ -1,18 +0,0 @@ -package slice - -// Chunk 返回分块后的切片 -func Chunk[T any](collection []T, size int) [][]T { - if len(collection) == 0 { - return nil - } - - if size < 1 { - panic("size must be greater than 0") - } - - result := make([][]T, 0, (len(collection)+size-1)/size) - for size < len(collection) { - collection, result = collection[size:], append(result, collection[0:size]) - } - return append(result, collection) -} diff --git a/utils/slice/chunk_test.go b/utils/slice/chunk_test.go deleted file mode 100644 index 334fdbe..0000000 --- a/utils/slice/chunk_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package slice_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/slice" - "testing" -) - -func TestChunk(t *testing.T) { - var collection = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} - var chunks = slice.Chunk(collection, 3) - for _, chunk := range chunks { - t.Log(chunk) - } -} - -func ExampleChunk() { - var collection = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} - var chunks = slice.Chunk(collection, 3) - for _, chunk := range chunks { - fmt.Println(chunk) - } - // Output: - // [1 2 3] - // [4 5 6] - // [7 8 9] -} - -func BenchmarkChunk(b *testing.B) { - var collection = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} - b.ResetTimer() - for i := 0; i < b.N; i++ { - slice.Chunk(collection, 3) - } -} diff --git a/utils/slice/drop.go b/utils/slice/drop.go deleted file mode 100644 index 3883121..0000000 --- a/utils/slice/drop.go +++ /dev/null @@ -1,40 +0,0 @@ -package slice - -// Drop 从切片中删除指定数量的元素 -// - start: 起始位置 -// - n: 删除的元素数量 -// - slice: 待删除元素的切片 -// -// 关于 start 的取值: -// - 当 start < 0 时,start 将会从右至左计数,即 -1 表示最后一个元素,-2 表示倒数第二个元素,以此类推 -func Drop[V any](start, n int, slice []V) []V { - var s = make([]V, len(slice)) - copy(s, slice) - if start < 0 { - start = len(s) + start - n + 1 - if start < 0 { - start = 0 - } - } - - end := start + n - if end > len(s) { - end = len(s) - } - - return append(s[:start], s[end:]...) -} - -// DropBy 从切片中删除指定的元素 -// - slice: 待删除元素的切片 -func DropBy[V any](slice []V, fn func(index int, value V) bool) []V { - var s = make([]V, len(slice)) - copy(s, slice) - for i := 0; i < len(s); i++ { - if fn(i, s[i]) { - s = append(s[:i], s[i+1:]...) - i-- - } - } - return s -} diff --git a/utils/slice/drop_test.go b/utils/slice/drop_test.go deleted file mode 100644 index a4c1aa1..0000000 --- a/utils/slice/drop_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package slice_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/slice" - "testing" -) - -func TestDrop(t *testing.T) { - s := []int{1, 2, 3, 4, 5} - t.Log(s, slice.Drop(1, 3, s)) -} - -func ExampleDrop() { - fmt.Println(slice.Drop(1, 3, []int{1, 2, 3, 4, 5})) - // Output: - // [1 5] -} - -func BenchmarkDrop(b *testing.B) { - s := []int{1, 2, 3, 4, 5} - b.ResetTimer() - for i := 0; i < b.N; i++ { - slice.Drop(1, 3, s) - } -} diff --git a/utils/slice/each.go b/utils/slice/each.go deleted file mode 100644 index 9174bf1..0000000 --- a/utils/slice/each.go +++ /dev/null @@ -1,85 +0,0 @@ -package slice - -// Each 根据传入的 abort 遍历 slice,如果 iterator 返回值与 abort 相同,则停止遍历 -func Each[V any](abort bool, slice []V, iterator func(index int, item V) bool) { - for i := 0; i < len(slice); i++ { - if iterator(i, slice[i]) == abort { - break - } - } -} - -// EachT 与 Each 的功能相同,但是 abort 被默认为 true -func EachT[V any](slice []V, iterator func(index int, item V) bool) { - Each(true, slice, iterator) -} - -// EachF 与 Each 的功能相同,但是 abort 被默认为 false -func EachF[V any](slice []V, iterator func(index int, item V) bool) { - Each(false, slice, iterator) -} - -// EachReverse 根据传入的 abort 从后往前遍历 slice,如果 iterator 返回值与 abort 相同,则停止遍历 -func EachReverse[V any](abort bool, slice []V, iterator func(index int, item V) bool) { - for i := len(slice) - 1; i >= 0; i-- { - if iterator(i, slice[i]) == abort { - break - } - } -} - -// EachReverseT 与 EachReverse 的功能相同,但是 abort 被默认为 true -func EachReverseT[V any](slice []V, iterator func(index int, item V) bool) { - EachReverse(true, slice, iterator) -} - -// EachReverseF 与 EachReverse 的功能相同,但是 abort 被默认为 false -func EachReverseF[V any](slice []V, iterator func(index int, item V) bool) { - EachReverse(false, slice, iterator) -} - -// EachResult 根据传入的 abort 遍历 slice,得到遍历的结果,如果 iterator 返回值中的 bool 值与 abort 相同,则停止遍历,并返回当前已积累的结果 -func EachResult[V any, R any](abort bool, slice []V, iterator func(index int, item V) (R, bool)) []R { - var result []R - for i := 0; i < len(slice); i++ { - r, ok := iterator(i, slice[i]) - result = append(result, r) - if ok == abort { - break - } - } - return result -} - -// EachResultT 与 EachResult 的功能相同,但是 abort 被默认为 true -func EachResultT[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { - return EachResult(true, slice, iterator) -} - -// EachResultF 与 EachResult 的功能相同,但是 abort 被默认为 false -func EachResultF[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { - return EachResult(false, slice, iterator) -} - -// EachResultReverse 根据传入的 abort 从后往前遍历 slice,得到遍历的结果,如果 iterator 返回值中的 bool 值与 abort 相同,则停止遍历,并返回当前已积累的结果 -func EachResultReverse[V any, R any](abort bool, slice []V, iterator func(index int, item V) (R, bool)) []R { - var result []R - for i := len(slice) - 1; i >= 0; i-- { - r, ok := iterator(i, slice[i]) - result = append(result, r) - if ok == abort { - break - } - } - return result -} - -// EachResultReverseT 与 EachResultReverse 的功能相同,但是 abort 被默认为 true -func EachResultReverseT[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { - return EachResultReverse(true, slice, iterator) -} - -// EachResultReverseF 与 EachResultReverse 的功能相同,但是 abort 被默认为 false -func EachResultReverseF[V any, R any](slice []V, iterator func(index int, item V) (R, bool)) []R { - return EachResultReverse(false, slice, iterator) -} diff --git a/utils/slice/fill.go b/utils/slice/fill.go deleted file mode 100644 index a345315..0000000 --- a/utils/slice/fill.go +++ /dev/null @@ -1,58 +0,0 @@ -package slice - -// FillBy 用指定的值填充切片 -// - slice: 待填充的切片 -func FillBy[V any](slice []V, fn func(index int, value V) V) []V { - for i, v := range slice { - slice[i] = fn(i, v) - } - return slice -} - -// FillByCopy 与 FillBy 的功能相同,但是不会改变原切片,而是返回一个新的切片 -func FillByCopy[V any](slice []V, fn func(index int, value V) V) []V { - var s = make([]V, len(slice)) - copy(s, slice) - return FillBy(s, fn) -} - -// FillUntil 用指定的值填充切片,如果 fn 返回的 bool 值与 abort 相同,则停止填充 -// - abort: 填充中止条件 -// - slice: 待填充的切片 -func FillUntil[V any](abort bool, slice []V, fn func(index int, value V) (V, bool)) []V { - for i, v := range slice { - if value, b := fn(i, v); b == abort { - break - } else { - slice[i] = value - } - } - return slice -} - -// FillUntilCopy 与 FillUntil 的功能相同,但不会改变原切片,而是返回一个新的切片 -func FillUntilCopy[V any](abort bool, slice []V, fn func(index int, value V) (V, bool)) []V { - var s = make([]V, len(slice)) - copy(s, slice) - return FillUntil(abort, s, fn) -} - -// FillUntilT 是 FillUntil 的简化版本,其中 abort 参数为 true -func FillUntilT[V any](slice []V, fn func(index int, value V) (V, bool)) []V { - return FillUntil(true, slice, fn) -} - -// FillUntilF 是 FillUntil 的简化版本,其中 abort 参数为 false -func FillUntilF[V any](slice []V, fn func(index int, value V) (V, bool)) []V { - return FillUntil(false, slice, fn) -} - -// FillUntilTCopy 是 FillUntilCopy 的简化版本,其中 abort 参数为 true -func FillUntilTCopy[V any](slice []V, fn func(index int, value V) (V, bool)) []V { - return FillUntilCopy(true, slice, fn) -} - -// FillUntilFCopy 是 FillUntilCopy 的简化版本,其中 abort 参数为 false -func FillUntilFCopy[V any](slice []V, fn func(index int, value V) (V, bool)) []V { - return FillUntilCopy(false, slice, fn) -} diff --git a/utils/slice/filter.go b/utils/slice/filter.go deleted file mode 100644 index 2a8a806..0000000 --- a/utils/slice/filter.go +++ /dev/null @@ -1,64 +0,0 @@ -package slice - -// Filter 根据特定的表达式过滤切片成员 -// - reserve: 是否保留符合条件的成员 -// - slice: 待过滤的切片 -// - expression: 过滤表达式 -// -// 这个函数的作用是遍历输入切片 slice,然后根据 expression 函数的返回值来决定是否保留每个元素。具体来说 -// - 如果 expression 返回 true 并且 reserve 也是 true,那么元素会被保留 -// - 如果 expression 返回 false 并且 reserve 是 false,那么元素也会被保留 -// -// 该没有创建新的内存空间或进行元素复制,所以整个操作相当高效。同时,由于 s 和 slice 实际上共享底层的数组,因此这个函数会改变传入的 slice。如果不希望改变原切片,需要在函数调用之前手动复制一份或者使用 FilterCopy 函数。 -func Filter[V any](reserve bool, slice []V, expression func(index int, item V) bool) []V { - if len(slice) == 0 { - return slice - } - - var ( - i int - j int - ) - for i = 0; i < len(slice); i++ { - if expression(i, slice[i]) { - if reserve { - slice[j] = slice[i] - j++ - } - } else { - if !reserve { - slice[j] = slice[i] - j++ - } - } - } - - return slice[:j] -} - -// FilterT 与 Filter 的功能相同,但是 reserve 被默认为 true -func FilterT[V any](slice []V, expression func(index int, item V) bool) []V { - return Filter(true, slice, expression) -} - -// FilterF 与 Filter 的功能相同,但是 reserve 被默认为 false -func FilterF[V any](slice []V, expression func(index int, item V) bool) []V { - return Filter(false, slice, expression) -} - -// FilterCopy 与 Filter 的功能相同,但是不会改变原切片,而是返回一个新的切片 -func FilterCopy[V any](reserve bool, slice []V, expression func(index int, item V) bool) []V { - var s = make([]V, len(slice)) - copy(s, slice) - return Filter(reserve, s, expression) -} - -// FilterTCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 true -func FilterTCopy[V any](slice []V, expression func(index int, item V) bool) []V { - return FilterCopy(true, slice, expression) -} - -// FilterFCopy 与 FilterCopy 的功能相同,但是 reserve 被默认为 false -func FilterFCopy[V any](slice []V, expression func(index int, item V) bool) []V { - return FilterCopy(false, slice, expression) -} diff --git a/utils/slice/filter_test.go b/utils/slice/filter_test.go deleted file mode 100644 index 1249821..0000000 --- a/utils/slice/filter_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package slice_test - -import ( - "github.com/kercylan98/minotaur/utils/slice" - "testing" -) - -func TestFilter(t *testing.T) { - s := []int{1, 2, 3, 4, 5} - s = slice.Filter(true, s, func(index int, item int) bool { - return item%2 == 0 - }) - if len(s) != 2 { - t.Error("Filter failed") - } - if s[0] != 2 { - t.Error("Filter failed") - } - if s[1] != 4 { - t.Error("Filter failed") - } -} - -func TestFilterCopy(t *testing.T) { - s := []int{1, 2, 3, 4, 5} - cp := slice.FilterCopy(true, s, func(index int, item int) bool { - return item%2 == 0 - }) - if len(s) != 5 { - t.Error("FilterCopy failed") - } else { - t.Log(s, cp) - } -} diff --git a/utils/slice/group.go b/utils/slice/group.go deleted file mode 100644 index 503333f..0000000 --- a/utils/slice/group.go +++ /dev/null @@ -1,17 +0,0 @@ -package slice - -// GroupBy 返回分组后的切片 -func GroupBy[T any, K comparable](collection []T, fn func(T) K) map[K][]T { - if len(collection) == 0 { - return nil - } - - result := make(map[K][]T, len(collection)) - - for _, item := range collection { - key := fn(item) - result[key] = append(result[key], item) - } - - return result -} diff --git a/utils/slice/map.go b/utils/slice/map.go deleted file mode 100644 index 9d3af4a..0000000 --- a/utils/slice/map.go +++ /dev/null @@ -1,14 +0,0 @@ -package slice - -// Map 将切片中的每一个元素转换为另一种类型的元素 -// - slice: 待转换的切片 -// - converter: 转换函数 -// -// 这不会改变原有的切片,而是返回一个新的切片 -func Map[V any, T any](slice []V, converter func(index int, item V) T) []T { - var s = make([]T, len(slice)) - for i := 0; i < len(slice); i++ { - s[i] = converter(i, slice[i]) - } - return s -} diff --git a/utils/slice/priority.go b/utils/slice/priority.go deleted file mode 100644 index 7d2afec..0000000 --- a/utils/slice/priority.go +++ /dev/null @@ -1,163 +0,0 @@ -package slice - -import ( - "fmt" - "sort" -) - -// NewPriority 创建一个优先级切片 -func NewPriority[V any](lengthAndCap ...int) *Priority[V] { - p := &Priority[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 -} - -// Priority 是一个优先级切片 -type Priority[V any] struct { - items []*PriorityItem[V] -} - -// Len 返回切片长度 -func (slf *Priority[V]) Len() int { - return len(slf.items) -} - -// Cap 返回切片容量 -func (slf *Priority[V]) Cap() int { - return cap(slf.items) -} - -// Clear 清空切片 -func (slf *Priority[V]) Clear() { - slf.items = slf.items[:0] -} - -// Append 添加元素 -func (slf *Priority[V]) Append(v V, priority int) { - slf.items = append(slf.items, NewPriorityItem[V](v, priority)) - slf.sort() -} - -// Appends 添加元素 -func (slf *Priority[V]) Appends(priority int, vs ...V) { - for _, v := range vs { - slf.Append(v, priority) - } - slf.sort() -} - -// Get 获取元素 -func (slf *Priority[V]) Get(index int) *PriorityItem[V] { - return slf.items[index] -} - -// GetValue 获取元素值 -func (slf *Priority[V]) GetValue(index int) V { - return slf.items[index].Value() -} - -// GetPriority 获取元素优先级 -func (slf *Priority[V]) GetPriority(index int) int { - return slf.items[index].Priority() -} - -// Set 设置元素 -func (slf *Priority[V]) Set(index int, value V, priority int) { - before := slf.items[index] - slf.items[index] = NewPriorityItem[V](value, priority) - if before.Priority() != priority { - slf.sort() - } -} - -// SetValue 设置元素值 -func (slf *Priority[V]) SetValue(index int, value V) { - slf.items[index].v = value -} - -// SetPriority 设置元素优先级 -func (slf *Priority[V]) SetPriority(index int, priority int) { - slf.items[index].p = priority - slf.sort() -} - -// Action 直接操作切片,如果返回值不为 nil,则替换切片 -func (slf *Priority[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 *Priority[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 *Priority[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 *Priority[V]) RangePriority(action func(index int, priority int) bool) { - slf.Range(func(index int, item *PriorityItem[V]) bool { - return action(index, item.Priority()) - }) -} - -// Slice 返回切片 -func (slf *Priority[V]) Slice() []V { - var vs []V - for _, item := range slf.items { - vs = append(vs, item.Value()) - } - return vs -} - -// String 返回切片字符串 -func (slf *Priority[V]) String() string { - var vs []V - for _, item := range slf.items { - vs = append(vs, item.Value()) - } - return fmt.Sprint(vs) -} - -// sort 排序 -func (slf *Priority[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] - } - } -} diff --git a/utils/slice/priority_item.go b/utils/slice/priority_item.go deleted file mode 100644 index 2bccbc2..0000000 --- a/utils/slice/priority_item.go +++ /dev/null @@ -1,34 +0,0 @@ -package slice - -// NewPriorityItem 创建一个优先级切片元素 -func NewPriorityItem[V any](v V, priority int) *PriorityItem[V] { - return &PriorityItem[V]{v: v, p: priority} -} - -// 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 -} diff --git a/utils/slice/priority_test.go b/utils/slice/priority_test.go deleted file mode 100644 index 873f3bb..0000000 --- a/utils/slice/priority_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package slice_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/slice" - "testing" -) - -func TestPriority_Append(t *testing.T) { - var s = slice.NewPriority[string]() - s.Append("name_1", 2) - s.Append("name_2", 1) - fmt.Println(s) -} diff --git a/utils/slice/reduce.go b/utils/slice/reduce.go deleted file mode 100644 index ae31edd..0000000 --- a/utils/slice/reduce.go +++ /dev/null @@ -1,28 +0,0 @@ -package slice - -import ( - "github.com/kercylan98/minotaur/utils/generic" -) - -// Reduce 将切片中的多个元素组合成一个单一值 -// - start: 开始索引,如果为负数则从后往前计算,例如:-1 表示从最后一个元素开始向左遍历,1 表示从第二个元素开始 -// - slice: 待组合的切片 -// - reducer: 组合函数 -func Reduce[V any, R generic.Number](start int, slice []V, reducer func(index int, item V, current R) R) (result R) { - length := len(slice) - - if start >= length || -start > length { - return - } - - if start < 0 { - for i := length + start; i >= 0; i-- { - result = reducer(i, slice[i], result) - } - } else { - for i := start; i < length; i++ { - result = reducer(i, slice[i], result) - } - } - return result -} diff --git a/utils/slice/reduce_test.go b/utils/slice/reduce_test.go deleted file mode 100644 index 3553fd9..0000000 --- a/utils/slice/reduce_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package slice_test - -import ( - "github.com/kercylan98/minotaur/utils/slice" - "testing" -) - -func TestReduce(t *testing.T) { - s := []int{1, 2, 3, 4, 5} - sum := slice.Reduce(0, s, func(index int, item int, current int) int { - return current + item - }) - t.Log(sum) - if sum != 15 { - t.Error("Reduce failed") - } -} diff --git a/utils/slice/shuffle.go b/utils/slice/shuffle.go deleted file mode 100644 index bb99be8..0000000 --- a/utils/slice/shuffle.go +++ /dev/null @@ -1,30 +0,0 @@ -package slice - -import "math/rand" - -// Shuffle 随机打乱切片 -// - 该函数会改变传入的切片,如果不希望改变原切片,需要在函数调用之前手动复制一份或者使用 ShuffleCopy 函数 -func Shuffle[T any](collection []T) []T { - rand.Shuffle(len(collection), func(i, j int) { - collection[i], collection[j] = collection[j], collection[i] - }) - - return collection -} - -// ShuffleCopy 返回随机打乱后的切片 -// - 该函数不会改变原切片 -func ShuffleCopy[T any](collection []T) []T { - if len(collection) == 0 { - return nil - } - - result := make([]T, len(collection)) - perm := rand.Perm(len(collection)) - - for i, randIndex := range perm { - result[i] = collection[randIndex] - } - - return result -} diff --git a/utils/slice/slice.go b/utils/slice/slice.go deleted file mode 100644 index da75a7a..0000000 --- a/utils/slice/slice.go +++ /dev/null @@ -1,328 +0,0 @@ -package slice - -import ( - "reflect" -) - -// Slice 切片类型 -type Slice[V any] []V - -// GetValue 获取特定索引的元素,如果索引超出范围则返回零值 -func GetValue[V any](slice []V, i int) (v V) { - if i >= 0 && i < len(slice) { - return slice[i] - } - return -} - -// GetValueHandle 获取特定索引的元素,并通过 handle 进入传入,如果索引超出范围则不会进入 handle -func GetValueHandle[V any](slice []V, i int, handle func(v V)) { - if i >= 0 && i < len(slice) { - handle(slice[i]) - } -} - -// Del 删除特定索引的元素 -func Del[V any](slice *[]V, index int) { - s := *slice - *slice = append(s[:index], s[index+1:]...) -} - -// Copy 复制特定切片 -// - 该函数已经可使用标准库 slices.Clone 代替,但是由于调用者可能繁多,所以该函数将不会被移除 -func Copy[S ~[]V, V any](slice S) S { - if slice == nil { - return nil - } - return append(slice[:0:0], slice...) -} - -// CopyMatrix 复制二维数组 -func CopyMatrix[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] = Copy(slice[i]) - } - return result -} - -// Insert 在特定索引插入元素 -func Insert[V any](slice *[]V, index int, value V) { - s := *slice - if index <= 0 { - *slice = append([]V{value}, s...) - } else if index >= len(s) { - *slice = append(s, value) - } else { - *slice = append(s[:index], append([]V{value}, s[index:]...)...) - } -} - -// Move 移动特定索引 -func Move[V any](slice *[]V, index, to int) { - s := *slice - v := s[index] - if index == to { - return - } else if to < index { - Del[V](slice, index) - Insert(slice, to, v) - } else { - Insert(slice, to, v) - Del[V](slice, index) - } -} - -// NextLoop 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始 -// - 当 i 为 -1 时将返回第一个元素 -func NextLoop[V any](slice []V, i int) (next int, value V) { - if i == -1 { - return 0, slice[0] - } - next = i + 1 - if next == len(slice) { - next = 0 - } - return next, slice[next] -} - -// PrevLoop 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始 -// - 当 i 为 -1 时将返回最后一个元素 -func PrevLoop[V any](slice []V, i int) (prev int, value V) { - if i == -1 { - return len(slice) - 1, slice[len(slice)-1] - } - prev = i - 1 - if prev == -1 { - prev = len(slice) - 1 - } - return prev, slice[prev] -} - -// Reverse 反转数组 -func Reverse[V any](slice []V) { - for i := 0; i < len(slice)/2; i++ { - slice[i], slice[len(slice)-1-i] = slice[len(slice)-1-i], slice[i] - } -} - -// Distinct 去重 -func Distinct[V any](slice []V) []V { - var result []V - for i := range slice { - flag := true - for j := range result { - if reflect.DeepEqual(slice[i], result[j]) { - flag = false - break - } - } - if flag { - result = append(result, slice[i]) - } - } - return result -} - -// Swap 交换数组中的两个元素 -func Swap[V any](slice []V, i, j int) { - slice[i], slice[j] = slice[j], slice[i] -} - -// ToMap 将数组转换为 map -func ToMap[K comparable, V any](slice []V, key func(V) K) map[K]V { - m := make(map[K]V) - for _, v := range slice { - m[key(v)] = v - } - return m -} - -// ToSet 将数组转换为 set -func ToSet[V comparable](slice []V) map[V]struct{} { - m := make(map[V]struct{}) - for _, v := range slice { - m[v] = struct{}{} - } - return m -} - -// Merge 合并多个数组 -func Merge[V any](slices ...[]V) []V { - var slice []V - for _, s := range slices { - slice = append(slice, s...) - } - return slice -} - -// GetStartPart 获取数组的前 n 个元素 -func GetStartPart[V any](slice []V, n int) []V { - if n > len(slice) { - n = len(slice) - } - return slice[:n] -} - -// GetEndPart 获取数组的后 n 个元素 -func GetEndPart[V any](slice []V, n int) []V { - if n > len(slice) { - n = len(slice) - } - return slice[len(slice)-n:] -} - -// GetPart 获取指定区间的元素 -func GetPart[V any](slice []V, start, end int) []V { - if start < 0 { - start = 0 - } - if end > len(slice) { - end = len(slice) - } - return slice[start:end] -} - -// Contains 判断数组是否包含某个元素 -func Contains[V comparable](slice []V, value V) bool { - for _, v := range slice { - if v == value { - return true - } - } - return false -} - -// ContainsAny 判断数组是否包含某个元素 -func ContainsAny[V any](slice []V, values V) bool { - for _, v := range slice { - if reflect.DeepEqual(v, values) { - return true - } - } - return false -} - -// GetIndex 判断数组是否包含某个元素,如果包含则返回索引 -func GetIndex[V comparable](slice []V, value V) int { - for i, v := range slice { - if v == value { - return i - } - } - return -1 -} - -// GetIndexAny 判断数组是否包含某个元素,如果包含则返回索引 -func GetIndexAny[V any](slice []V, values V) int { - for i, v := range slice { - if reflect.DeepEqual(v, values) { - return i - } - } - return -1 -} - -// Combinations 获取给定数组的所有组合,包括重复元素的组合 -func Combinations[T any](a []T) [][]T { - var result [][]T - n := len(a) - for i := 0; i < (1 << n); i++ { - var currentCombination []T - for j := 0; j < n; j++ { - if (i & (1 << j)) != 0 { - currentCombination = append(currentCombination, a[j]) - } - } - result = append(result, currentCombination) - } - return result -} - -// CombinationsPari 从给定的两个数组中按照特定数量得到所有组合后,再将两个数组的组合进行组合 -// - 例如从数组 A 中得到所有数量为2的组合,从数组 B 中得到所有数量为3的组合,再将两个组合进行组合,得到所有数量为5的组合 -func CombinationsPari[T any](a, b []T, aCount, bCount int) [][]T { - var combinations [][]T - var as, bs = LimitedCombinations(a, aCount, aCount), LimitedCombinations(b, bCount, bCount) - for _, asi := range as { - for _, bsi := range bs { - combinations = append(combinations, append(asi, bsi...)) - } - } - return combinations -} - -// LimitedCombinations 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 -func LimitedCombinations[T any](a []T, minSize, maxSize int) [][]T { - n := len(a) - if n == 0 || minSize <= 0 || maxSize <= 0 || minSize > maxSize { - return nil - } - - var result [][]T - var currentCombination []T - - var backtrack func(startIndex int, currentSize int) - backtrack = func(startIndex int, currentSize int) { - if currentSize >= minSize && currentSize <= maxSize { - combination := make([]T, len(currentCombination)) - copy(combination, currentCombination) - result = append(result, combination) - } - - for i := startIndex; i < n; i++ { - currentCombination = append(currentCombination, a[i]) - backtrack(i+1, currentSize+1) - currentCombination = currentCombination[:len(currentCombination)-1] - } - } - - backtrack(0, 0) - return result -} - -// IsIntersectWithCheck 判断两个切片是否有交集 -func IsIntersectWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) bool { - for _, a := range a { - for _, b := range b { - if checkHandle(a, b) { - return true - } - } - } - return false -} - -// IsIntersect 判断两个切片是否有交集 -func IsIntersect[T any](a, b []T) bool { - for _, a := range a { - for _, b := range b { - if reflect.DeepEqual(a, b) { - return true - } - } - } - return false -} - -// SubWithCheck 获取移除指定元素后的切片 -// - checkHandle 返回 true 表示需要移除 -func SubWithCheck[T any](a, b []T, checkHandle func(a, b T) bool) []T { - var result []T - for _, a := range a { - flag := false - for _, b := range b { - if checkHandle(a, b) { - flag = true - break - } - } - if !flag { - result = append(result, a) - } - } - return result -} diff --git a/utils/slice/slice_test.go b/utils/slice/slice_test.go deleted file mode 100644 index b9b1f26..0000000 --- a/utils/slice/slice_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package slice_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/slice" - "testing" -) - -func TestLimitedCombinations(t *testing.T) { - c := slice.LimitedCombinations([]int{1, 2, 3, 4, 5}, 3, 3) - for _, v := range c { - fmt.Println(v) - } -} diff --git a/utils/slice/unique.go b/utils/slice/unique.go deleted file mode 100644 index cca9197..0000000 --- a/utils/slice/unique.go +++ /dev/null @@ -1,44 +0,0 @@ -package slice - -// Unique 返回去重后的切片 -func Unique[T comparable](collection []T) []T { - if len(collection) == 0 { - return nil - } - - result := make([]T, 0, len(collection)) - seen := make(map[T]struct{}, len(collection)) - - for _, item := range collection { - if _, ok := seen[item]; ok { - continue - } - - seen[item] = struct{}{} - result = append(result, item) - } - - return result -} - -// UniqueBy 返回去重后的切片 -func UniqueBy[T any](collection []T, fn func(T) any) []T { - if len(collection) == 0 { - return nil - } - - result := make([]T, 0, len(collection)) - seen := make(map[any]struct{}, len(collection)) - - for _, item := range collection { - key := fn(item) - if _, ok := seen[key]; ok { - continue - } - - seen[key] = struct{}{} - result = append(result, item) - } - - return result -} diff --git a/utils/slice/zoom.go b/utils/slice/zoom.go deleted file mode 100644 index 567d3d3..0000000 --- a/utils/slice/zoom.go +++ /dev/null @@ -1,11 +0,0 @@ -package slice - -// Zoom 将切片的长度缩放到指定的大小,如果 newSize 小于 slice 的长度,则会截断 slice,如果 newSize 大于 slice 的长度,则会在 slice 的末尾添加零值数据 -func Zoom[V any](newSize int, slice []V) []V { - if newSize < 0 { - newSize = 0 - } - var s = make([]V, newSize) - copy(s, slice) - return s -} diff --git a/utils/sole/once.go b/utils/sole/once.go index 9d06def..9504478 100644 --- a/utils/sole/once.go +++ b/utils/sole/once.go @@ -1,6 +1,8 @@ package sole -import "github.com/kercylan98/minotaur/utils/hash" +import ( + "github.com/kercylan98/minotaur/utils/collection" +) // NewOnce 创建一个用于数据取值去重的结构实例 func NewOnce[V any]() *Once[V] { @@ -33,5 +35,5 @@ func (slf *Once[V]) Reset(key ...any) { } return } - hash.Clear(slf.r) + collection.ClearMap(slf.r) } diff --git a/utils/sorts/topological.go b/utils/sorts/topological.go index 920b609..f147ef9 100644 --- a/utils/sorts/topological.go +++ b/utils/sorts/topological.go @@ -12,7 +12,7 @@ type topologicalNode[V any] struct { // - queryDependsHandler: 用于查询切片中每个元素的依赖关系,返回的是一个索引切片,如果没有依赖关系,那么返回空切片 // // 该函数在存在循环依赖的情况下将会返回 ErrCircularDependencyDetected 错误 -func Topological[Index comparable, V any](slice []V, queryIndexHandler func(item V) Index, queryDependsHandler func(item V) []Index) ([]V, error) { +func Topological[S ~[]V, Index comparable, V any](slice S, queryIndexHandler func(item V) Index, queryDependsHandler func(item V) []Index) (S, error) { var nodes = make(map[Index]*topologicalNode[V]) diff --git a/utils/stream/map.go b/utils/stream/map.go deleted file mode 100644 index ba4d398..0000000 --- a/utils/stream/map.go +++ /dev/null @@ -1,93 +0,0 @@ -package stream - -import ( - "github.com/kercylan98/minotaur/utils/hash" -) - -// WithMap 将映射转换为流 -func WithMap[K comparable, V any](m map[K]V) Map[K, V] { - return m -} - -type Map[K comparable, V any] map[K]V - -// Map 返回映射 -func (slf Map[K, V]) Map() map[K]V { - return slf -} - -// Keys 返回键列表 -func (slf Map[K, V]) Keys() Slice[K] { - var res = make([]K, 0, len(slf)) - for key := range slf { - res = append(res, key) - } - return res -} - -// Values 返回值列表 -func (slf Map[K, V]) Values() Slice[V] { - var res = make([]V, 0, len(slf)) - for _, value := range slf { - res = append(res, value) - } - return res -} - -// Filter 是 hash.Filter 的快捷方式 -func (slf Map[K, V]) Filter(reserve bool, expression func(key K, value V) bool) Map[K, V] { - return hash.Filter(reserve, slf, expression) -} - -// FilterT 是 hash.FilterT 的快捷方式 -func (slf Map[K, V]) FilterT(expression func(key K, value V) bool) Map[K, V] { - return hash.FilterT(slf, expression) -} - -// FilterF 是 hash.FilterF 的快捷方式 -func (slf Map[K, V]) FilterF(expression func(key K, value V) bool) Map[K, V] { - return hash.FilterF(slf, expression) -} - -// FilterCopy 是 hash.FilterCopy 的快捷方式 -func (slf Map[K, V]) FilterCopy(reserve bool, expression func(key K, value V) bool) Map[K, V] { - return hash.FilterCopy(reserve, slf, expression) -} - -// FilterTCopy 是 hash.FilterTCopy 的快捷方式 -func (slf Map[K, V]) FilterTCopy(expression func(key K, value V) bool) Map[K, V] { - return hash.FilterTCopy(slf, expression) -} - -// FilterFCopy 是 hash.FilterFCopy 的快捷方式 -func (slf Map[K, V]) FilterFCopy(expression func(key K, value V) bool) Map[K, V] { - return hash.FilterFCopy(slf, expression) -} - -// Chunk 是 hash.Chunk 的快捷方式 -func (slf Map[K, V]) Chunk(size int) Maps[K, V] { - chunks := hash.Chunk(slf, size) - var res = make([]Map[K, V], 0, len(chunks)) - for i := range chunks { - res = append(res, chunks[i]) - } - return res -} - -// Each 是 hash.Each 的快捷方式 -func (slf Map[K, V]) Each(abort bool, iterator func(i int, key K, item V) bool) Map[K, V] { - hash.Each(abort, slf, iterator) - return slf -} - -// EachT 是 hash.EachT 的快捷方式 -func (slf Map[K, V]) EachT(iterator func(i int, key K, item V) bool) Map[K, V] { - hash.EachT(slf, iterator) - return slf -} - -// EachF 是 hash.EachF 的快捷方式 -func (slf Map[K, V]) EachF(iterator func(i int, key K, item V) bool) Map[K, V] { - hash.EachF(slf, iterator) - return slf -} diff --git a/utils/stream/map_test.go b/utils/stream/map_test.go deleted file mode 100644 index c037496..0000000 --- a/utils/stream/map_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package stream_test - -import ( - "github.com/kercylan98/minotaur/utils/stream" - "testing" -) - -func TestMap_Chunk(t *testing.T) { - var m = map[string]int{ - "a": 1, - "b": 2, - } - t.Log(stream.WithMap(m).Chunk(1).Merge()) -} diff --git a/utils/stream/maps.go b/utils/stream/maps.go deleted file mode 100644 index 96503a1..0000000 --- a/utils/stream/maps.go +++ /dev/null @@ -1,166 +0,0 @@ -package stream - -import "github.com/kercylan98/minotaur/utils/slice" - -// WithMaps 将多个 map 转换为流 -func WithMaps[K comparable, V any](maps ...map[K]V) Maps[K, V] { - var ms = make(Maps[K, V], len(maps)) - for i, m := range maps { - ms[i] = m - } - return ms -} - -// WithStreamMaps 将多个 map 转换为流 -func WithStreamMaps[K comparable, V any](maps ...Map[K, V]) Maps[K, V] { - var ms = make(Maps[K, V], len(maps)) - for i, m := range maps { - ms[i] = m - } - return ms -} - -type Maps[K comparable, V any] []Map[K, V] - -// Merge 将多个 map 合并为一个 map,返回该 map 的流 -// - 当多个 map 中存在相同 key 的元素时,将会发生随机覆盖 -func (slf Maps[K, V]) Merge() Map[K, V] { - var m = make(map[K]V) - for _, stream := range slf { - for k, v := range stream { - m[k] = v - } - } - return m -} - -// Drop 是 slice.Drop 的快捷方式 -func (slf Maps[K, V]) Drop(start, n int) Maps[K, V] { - return slice.Drop(start, n, slf) -} - -// DropBy 是 slice.DropBy 的快捷方式 -func (slf Maps[K, V]) DropBy(fn func(index int, value Map[K, V]) bool) Maps[K, V] { - return slice.DropBy(slf, fn) -} - -// Each 是 slice.Each 的快捷方式 -func (slf Maps[K, V]) Each(abort bool, iterator func(index int, item Map[K, V]) bool) Maps[K, V] { - slice.Each(abort, slf, iterator) - return slf -} - -// EachT 是 slice.EachT 的快捷方式 -func (slf Maps[K, V]) EachT(iterator func(index int, item Map[K, V]) bool) Maps[K, V] { - slice.EachT(slf, iterator) - return slf -} - -// EachF 是 slice.EachF 的快捷方式 -func (slf Maps[K, V]) EachF(iterator func(index int, item Map[K, V]) bool) Maps[K, V] { - slice.EachF(slf, iterator) - return slf -} - -// EachReverse 是 slice.EachReverse 的快捷方式 -func (slf Maps[K, V]) EachReverse(abort bool, iterator func(index int, item Map[K, V]) bool) Maps[K, V] { - slice.EachReverse(abort, slf, iterator) - return slf -} - -// EachReverseT 是 slice.EachReverseT 的快捷方式 -func (slf Maps[K, V]) EachReverseT(iterator func(index int, item Map[K, V]) bool) Maps[K, V] { - slice.EachReverseT(slf, iterator) - return slf -} - -// EachReverseF 是 slice.EachReverseF 的快捷方式 -func (slf Maps[K, V]) EachReverseF(iterator func(index int, item Map[K, V]) bool) Maps[K, V] { - slice.EachReverseF(slf, iterator) - return slf -} - -// FillBy 是 slice.FillBy 的快捷方式 -func (slf Maps[K, V]) FillBy(fn func(index int, value Map[K, V]) Map[K, V]) Maps[K, V] { - return slice.FillBy(slf, fn) -} - -// FillByCopy 是 slice.FillByCopy 的快捷方式 -func (slf Maps[K, V]) FillByCopy(fn func(index int, value Map[K, V]) Map[K, V]) Maps[K, V] { - return slice.FillByCopy(slf, fn) -} - -// FillUntil 是 slice.FillUntil 的快捷方式 -func (slf Maps[K, V]) FillUntil(abort bool, fn func(index int, value Map[K, V]) (Map[K, V], bool)) Maps[K, V] { - return slice.FillUntil(abort, slf, fn) -} - -// FillUntilCopy 是 slice.FillUntilCopy 的快捷方式 -func (slf Maps[K, V]) FillUntilCopy(abort bool, fn func(index int, value Map[K, V]) (Map[K, V], bool)) Maps[K, V] { - return slice.FillUntilCopy(abort, slf, fn) -} - -// FillUntilT 是 slice.FillUntilT 的快捷方式 -func (slf Maps[K, V]) FillUntilT(fn func(index int, value Map[K, V]) (Map[K, V], bool)) Maps[K, V] { - return slice.FillUntilT(slf, fn) -} - -// FillUntilF 是 slice.FillUntilF 的快捷方式 -func (slf Maps[K, V]) FillUntilF(fn func(index int, value Map[K, V]) (Map[K, V], bool)) Maps[K, V] { - return slice.FillUntilF(slf, fn) -} - -// FillUntilTCopy 是 slice.FillUntilTCopy 的快捷方式 -func (slf Maps[K, V]) FillUntilTCopy(fn func(index int, value Map[K, V]) (Map[K, V], bool)) Maps[K, V] { - return slice.FillUntilTCopy(slf, fn) -} - -// FillUntilFCopy 是 slice.FillUntilFCopy 的快捷方式 -func (slf Maps[K, V]) FillUntilFCopy(fn func(index int, value Map[K, V]) (Map[K, V], bool)) Maps[K, V] { - return slice.FillUntilFCopy(slf, fn) -} - -// Filter 是 slice.Filter 的快捷方式 -func (slf Maps[K, V]) Filter(reserve bool, expression func(index int, item Map[K, V]) bool) Maps[K, V] { - return slice.Filter(reserve, slf, expression) -} - -// FilterT 是 slice.FilterT 的快捷方式 -func (slf Maps[K, V]) FilterT(expression func(index int, item Map[K, V]) bool) Maps[K, V] { - return slice.FilterT(slf, expression) -} - -// FilterF 是 slice.FilterF 的快捷方式 -func (slf Maps[K, V]) FilterF(expression func(index int, item Map[K, V]) bool) Maps[K, V] { - return slice.FilterF(slf, expression) -} - -// FilterCopy 是 slice.FilterCopy 的快捷方式 -func (slf Maps[K, V]) FilterCopy(reserve bool, expression func(index int, item Map[K, V]) bool) Maps[K, V] { - return slice.FilterCopy(reserve, slf, expression) -} - -// FilterTCopy 是 slice.FilterTCopy 的快捷方式 -func (slf Maps[K, V]) FilterTCopy(expression func(index int, item Map[K, V]) bool) Maps[K, V] { - return slice.FilterTCopy(slf, expression) -} - -// FilterFCopy 是 slice.FilterFCopy 的快捷方式 -func (slf Maps[K, V]) FilterFCopy(expression func(index int, item Map[K, V]) bool) Maps[K, V] { - return slice.FilterFCopy(slf, expression) -} - -// Shuffle 是 slice.Shuffle 的快捷方式 -func (slf Maps[K, V]) Shuffle() Maps[K, V] { - return slice.Shuffle(slf) -} - -// ShuffleCopy 是 slice.ShuffleCopy 的快捷方式 -func (slf Maps[K, V]) ShuffleCopy() Maps[K, V] { - return slice.ShuffleCopy(slf) -} - -// UniqueBy 是 slice.UniqueBy 的快捷方式 -func (slf Maps[K, V]) UniqueBy(fn func(Map[K, V]) any) Maps[K, V] { - return slice.UniqueBy(slf, fn) -} diff --git a/utils/stream/slice.go b/utils/stream/slice.go deleted file mode 100644 index 43d8b28..0000000 --- a/utils/stream/slice.go +++ /dev/null @@ -1,184 +0,0 @@ -package stream - -import "github.com/kercylan98/minotaur/utils/slice" - -// WithSlice 将切片转换为流 -func WithSlice[V any](slice []V) Slice[V] { - return slice -} - -type Slice[V any] []V - -// Slice 返回切片 -func (slf Slice[V]) Slice() []V { - return slf -} - -// Copy 复制一份切片 -func (slf Slice[V]) Copy() Slice[V] { - return slice.Copy(slf) -} - -// Zoom 是 slice.Zoom 的快捷方式 -func (slf Slice[V]) Zoom(newSize int) Slice[V] { - return slice.Zoom(newSize, slf) -} - -// Indexes 将切片转换为索引切片 -func (slf Slice[V]) Indexes() Slice[int] { - var s = make([]int, len(slf)) - for i := 0; i < len(s); i++ { - s[i] = i - } - return s -} - -// Map 将切片转为 map -func (slf Slice[V]) Map() Map[int, V] { - var m = make(map[int]V, len(slf)) - for k, v := range slf { - m[k] = v - } - return m -} - -// Chunk 是 slice.Chunk 的快捷方式 -func (slf Slice[V]) Chunk(size int) Slices[V] { - chunks := slice.Chunk(slf, size) - result := make(Slices[V], len(chunks)) - for i, chunk := range chunks { - result[i] = chunk - } - return result -} - -// Drop 是 slice.Drop 的快捷方式 -func (slf Slice[V]) Drop(start, n int) Slice[V] { - return slice.Drop(start, n, slf) -} - -// DropBy 是 slice.DropBy 的快捷方式 -func (slf Slice[V]) DropBy(fn func(index int, value V) bool) Slice[V] { - return slice.DropBy(slf, fn) -} - -// Each 是 slice.Each 的快捷方式 -func (slf Slice[V]) Each(abort bool, iterator func(index int, item V) bool) Slice[V] { - slice.Each(abort, slf, iterator) - return slf -} - -// EachT 是 slice.EachT 的快捷方式 -func (slf Slice[V]) EachT(iterator func(index int, item V) bool) Slice[V] { - slice.EachT(slf, iterator) - return slf -} - -// EachF 是 slice.EachF 的快捷方式 -func (slf Slice[V]) EachF(iterator func(index int, item V) bool) Slice[V] { - slice.EachF(slf, iterator) - return slf -} - -// EachReverse 是 slice.EachReverse 的快捷方式 -func (slf Slice[V]) EachReverse(abort bool, iterator func(index int, item V) bool) Slice[V] { - slice.EachReverse(abort, slf, iterator) - return slf -} - -// EachReverseT 是 slice.EachReverseT 的快捷方式 -func (slf Slice[V]) EachReverseT(iterator func(index int, item V) bool) Slice[V] { - slice.EachReverseT(slf, iterator) - return slf -} - -// EachReverseF 是 slice.EachReverseF 的快捷方式 -func (slf Slice[V]) EachReverseF(iterator func(index int, item V) bool) Slice[V] { - slice.EachReverseF(slf, iterator) - return slf -} - -// FillBy 是 slice.FillBy 的快捷方式 -func (slf Slice[V]) FillBy(fn func(index int, value V) V) Slice[V] { - return slice.FillBy(slf, fn) -} - -// FillByCopy 是 slice.FillByCopy 的快捷方式 -func (slf Slice[V]) FillByCopy(fn func(index int, value V) V) Slice[V] { - return slice.FillByCopy(slf, fn) -} - -// FillUntil 是 slice.FillUntil 的快捷方式 -func (slf Slice[V]) FillUntil(abort bool, fn func(index int, value V) (V, bool)) Slice[V] { - return slice.FillUntil(abort, slf, fn) -} - -// FillUntilCopy 是 slice.FillUntilCopy 的快捷方式 -func (slf Slice[V]) FillUntilCopy(abort bool, fn func(index int, value V) (V, bool)) Slice[V] { - return slice.FillUntilCopy(abort, slf, fn) -} - -// FillUntilT 是 slice.FillUntilT 的快捷方式 -func (slf Slice[V]) FillUntilT(fn func(index int, value V) (V, bool)) Slice[V] { - return slice.FillUntilT(slf, fn) -} - -// FillUntilF 是 slice.FillUntilF 的快捷方式 -func (slf Slice[V]) FillUntilF(fn func(index int, value V) (V, bool)) Slice[V] { - return slice.FillUntilF(slf, fn) -} - -// FillUntilTCopy 是 slice.FillUntilTCopy 的快捷方式 -func (slf Slice[V]) FillUntilTCopy(fn func(index int, value V) (V, bool)) Slice[V] { - return slice.FillUntilTCopy(slf, fn) -} - -// FillUntilFCopy 是 slice.FillUntilFCopy 的快捷方式 -func (slf Slice[V]) FillUntilFCopy(fn func(index int, value V) (V, bool)) Slice[V] { - return slice.FillUntilFCopy(slf, fn) -} - -// Filter 是 slice.Filter 的快捷方式 -func (slf Slice[V]) Filter(reserve bool, expression func(index int, item V) bool) Slice[V] { - return slice.Filter(reserve, slf, expression) -} - -// FilterT 是 slice.FilterT 的快捷方式 -func (slf Slice[V]) FilterT(expression func(index int, item V) bool) Slice[V] { - return slice.FilterT(slf, expression) -} - -// FilterF 是 slice.FilterF 的快捷方式 -func (slf Slice[V]) FilterF(expression func(index int, item V) bool) Slice[V] { - return slice.FilterF(slf, expression) -} - -// FilterCopy 是 slice.FilterCopy 的快捷方式 -func (slf Slice[V]) FilterCopy(reserve bool, expression func(index int, item V) bool) Slice[V] { - return slice.FilterCopy(reserve, slf, expression) -} - -// FilterTCopy 是 slice.FilterTCopy 的快捷方式 -func (slf Slice[V]) FilterTCopy(expression func(index int, item V) bool) Slice[V] { - return slice.FilterTCopy(slf, expression) -} - -// FilterFCopy 是 slice.FilterFCopy 的快捷方式 -func (slf Slice[V]) FilterFCopy(expression func(index int, item V) bool) Slice[V] { - return slice.FilterFCopy(slf, expression) -} - -// Shuffle 是 slice.Shuffle 的快捷方式 -func (slf Slice[V]) Shuffle() Slice[V] { - return slice.Shuffle(slf) -} - -// ShuffleCopy 是 slice.ShuffleCopy 的快捷方式 -func (slf Slice[V]) ShuffleCopy() Slice[V] { - return slice.ShuffleCopy(slf) -} - -// UniqueBy 是 slice.UniqueBy 的快捷方式 -func (slf Slice[V]) UniqueBy(fn func(V) any) Slice[V] { - return slice.UniqueBy(slf, fn) -} diff --git a/utils/stream/slice_test.go b/utils/stream/slice_test.go deleted file mode 100644 index 83e5cac..0000000 --- a/utils/stream/slice_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package stream_test - -import ( - "github.com/kercylan98/minotaur/utils/stream" - "testing" -) - -func TestStream(t *testing.T) { - var s = stream.Slice[int]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} - t.Log(s, s. - Copy(). - Shuffle(). - Filter(true, func(index int, item int) bool { - return item%2 == 0 - }). - Zoom(20). - Each(true, func(index int, item int) bool { - t.Log(index, item) - return false - }). - Chunk(3). - EachT(func(index int, item stream.Slice[int]) bool { - t.Log(item) - return false - }). - Merge(). - FillBy(func(index int, value int) int { - if value == 0 { - return 999 - } - return value - }), - ) -} diff --git a/utils/stream/slices.go b/utils/stream/slices.go deleted file mode 100644 index 2b42c3c..0000000 --- a/utils/stream/slices.go +++ /dev/null @@ -1,163 +0,0 @@ -package stream - -import "github.com/kercylan98/minotaur/utils/slice" - -// WithSlices 将多个切片转换为流 -func WithSlices[V any](slices ...[]V) Slices[V] { - var ss = make(Slices[V], len(slices)) - for i, s := range slices { - ss[i] = s - } - return ss -} - -// WithStreamSlices 将多个切片转换为流 -func WithStreamSlices[V any](slices ...Slice[V]) Slices[V] { - var ss = make(Slices[V], len(slices)) - for i, s := range slices { - ss[i] = s - } - return ss -} - -type Slices[V any] []Slice[V] - -// Merge 合并为一个切片 -func (slf Slices[V]) Merge() Slice[V] { - var s = make([]V, 0) - for _, stream := range slf { - s = append(s, stream...) - } - return s -} - -// Drop 是 slice.Drop 的快捷方式 -func (slf Slices[V]) Drop(start, n int) Slices[V] { - return slice.Drop(start, n, slf) -} - -// DropBy 是 slice.DropBy 的快捷方式 -func (slf Slices[V]) DropBy(fn func(index int, value Slice[V]) bool) Slices[V] { - return slice.DropBy(slf, fn) -} - -// Each 是 slice.Each 的快捷方式 -func (slf Slices[V]) Each(abort bool, iterator func(index int, item Slice[V]) bool) Slices[V] { - slice.Each(abort, slf, iterator) - return slf -} - -// EachT 是 slice.EachT 的快捷方式 -func (slf Slices[V]) EachT(iterator func(index int, item Slice[V]) bool) Slices[V] { - slice.EachT(slf, iterator) - return slf -} - -// EachF 是 slice.EachF 的快捷方式 -func (slf Slices[V]) EachF(iterator func(index int, item Slice[V]) bool) Slices[V] { - slice.EachF(slf, iterator) - return slf -} - -// EachReverse 是 slice.EachReverse 的快捷方式 -func (slf Slices[V]) EachReverse(abort bool, iterator func(index int, item Slice[V]) bool) Slices[V] { - slice.EachReverse(abort, slf, iterator) - return slf -} - -// EachReverseT 是 slice.EachReverseT 的快捷方式 -func (slf Slices[V]) EachReverseT(iterator func(index int, item Slice[V]) bool) Slices[V] { - slice.EachReverseT(slf, iterator) - return slf -} - -// EachReverseF 是 slice.EachReverseF 的快捷方式 -func (slf Slices[V]) EachReverseF(iterator func(index int, item Slice[V]) bool) Slices[V] { - slice.EachReverseF(slf, iterator) - return slf -} - -// FillBy 是 slice.FillBy 的快捷方式 -func (slf Slices[V]) FillBy(fn func(index int, value Slice[V]) Slice[V]) Slices[V] { - return slice.FillBy(slf, fn) -} - -// FillByCopy 是 slice.FillByCopy 的快捷方式 -func (slf Slices[V]) FillByCopy(fn func(index int, value Slice[V]) Slice[V]) Slices[V] { - return slice.FillByCopy(slf, fn) -} - -// FillUntil 是 slice.FillUntil 的快捷方式 -func (slf Slices[V]) FillUntil(abort bool, fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { - return slice.FillUntil(abort, slf, fn) -} - -// FillUntilCopy 是 slice.FillUntilCopy 的快捷方式 -func (slf Slices[V]) FillUntilCopy(abort bool, fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { - return slice.FillUntilCopy(abort, slf, fn) -} - -// FillUntilT 是 slice.FillUntilT 的快捷方式 -func (slf Slices[V]) FillUntilT(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { - return slice.FillUntilT(slf, fn) -} - -// FillUntilF 是 slice.FillUntilF 的快捷方式 -func (slf Slices[V]) FillUntilF(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { - return slice.FillUntilF(slf, fn) -} - -// FillUntilTCopy 是 slice.FillUntilTCopy 的快捷方式 -func (slf Slices[V]) FillUntilTCopy(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { - return slice.FillUntilTCopy(slf, fn) -} - -// FillUntilFCopy 是 slice.FillUntilFCopy 的快捷方式 -func (slf Slices[V]) FillUntilFCopy(fn func(index int, value Slice[V]) (Slice[V], bool)) Slices[V] { - return slice.FillUntilFCopy(slf, fn) -} - -// Filter 是 slice.Filter 的快捷方式 -func (slf Slices[V]) Filter(reserve bool, expression func(index int, item Slice[V]) bool) Slices[V] { - return slice.Filter(reserve, slf, expression) -} - -// FilterT 是 slice.FilterT 的快捷方式 -func (slf Slices[V]) FilterT(expression func(index int, item Slice[V]) bool) Slices[V] { - return slice.FilterT(slf, expression) -} - -// FilterF 是 slice.FilterF 的快捷方式 -func (slf Slices[V]) FilterF(expression func(index int, item Slice[V]) bool) Slices[V] { - return slice.FilterF(slf, expression) -} - -// FilterCopy 是 slice.FilterCopy 的快捷方式 -func (slf Slices[V]) FilterCopy(reserve bool, expression func(index int, item Slice[V]) bool) Slices[V] { - return slice.FilterCopy(reserve, slf, expression) -} - -// FilterTCopy 是 slice.FilterTCopy 的快捷方式 -func (slf Slices[V]) FilterTCopy(expression func(index int, item Slice[V]) bool) Slices[V] { - return slice.FilterTCopy(slf, expression) -} - -// FilterFCopy 是 slice.FilterFCopy 的快捷方式 -func (slf Slices[V]) FilterFCopy(expression func(index int, item Slice[V]) bool) Slices[V] { - return slice.FilterFCopy(slf, expression) -} - -// Shuffle 是 slice.Shuffle 的快捷方式 -func (slf Slices[V]) Shuffle() Slices[V] { - return slice.Shuffle(slf) -} - -// ShuffleCopy 是 slice.ShuffleCopy 的快捷方式 -func (slf Slices[V]) ShuffleCopy() Slices[V] { - return slice.ShuffleCopy(slf) -} - -// UniqueBy 是 slice.UniqueBy 的快捷方式 -func (slf Slices[V]) UniqueBy(fn func(Slice[V]) any) Slices[V] { - return slice.UniqueBy(slf, fn) -} diff --git a/utils/times/line.go b/utils/times/line.go index ca89c2c..c346f6d 100644 --- a/utils/times/line.go +++ b/utils/times/line.go @@ -2,8 +2,8 @@ package times import ( "fmt" + "github.com/kercylan98/minotaur/utils/collection" "github.com/kercylan98/minotaur/utils/generic" - "github.com/kercylan98/minotaur/utils/slice" "strings" "time" ) @@ -62,7 +62,7 @@ func (slf *StateLine[State]) Check(missingAllowed bool, states ...State) bool { func (slf *StateLine[State]) GetMissingStates(states ...State) []State { var missing = make([]State, 0, len(states)) for _, state := range states { - if !slice.Contains(slf.states, state) { + if !collection.InComparableSlice(slf.states, state) { missing = append(missing, state) } } @@ -71,7 +71,7 @@ func (slf *StateLine[State]) GetMissingStates(states ...State) []State { // HasState 检查时间线中是否包含指定状态 func (slf *StateLine[State]) HasState(state State) bool { - return slice.Contains(slf.states, state) + return collection.InComparableSlice(slf.states, state) } // String 获取时间线的字符串表示 @@ -86,7 +86,7 @@ func (slf *StateLine[State]) String() string { // AddState 添加一个状态到时间线中,状态不能与任一时间点重合,否则将被忽略 // - onTrigger: 该状态绑定的触发器,该触发器不会被主动执行,需要主动获取触发器执行 func (slf *StateLine[State]) AddState(state State, t time.Time, onTrigger ...func()) *StateLine[State] { - if slice.Contains(slf.states, state) { + if collection.InComparableSlice(slf.states, state) { return slf } // 将 t 按照从左到右由早到晚的顺序插入到 points 中