diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2387e63..0555ddb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,4 +18,4 @@ jobs: bump-minor-pre-major: true bump-patch-for-minor-pre-major: true changelog-types: '[{"type":"other","section":"Other | 其他更改","hidden":false},{"type":"revert","section":"Reverts | 回退","hidden":false},{"type":"feat","section":"Features | 新特性","hidden":false},{"type":"fix","section":"Bug Fixes | 修复","hidden":false},{"type":"improvement","section":"Feature Improvements | 改进","hidden":false},{"type":"docs","section":"Docs | 文档优化","hidden":false},{"type":"style","section":"Styling | 可读性优化","hidden":false},{"type":"refactor","section":"Code Refactoring | 重构","hidden":false},{"type":"perf","section":"Performance Improvements | 性能优化","hidden":false},{"type":"test","section":"Tests | 新增或优化测试用例","hidden":false},{"type":"build","section":"Build System | 影响构建的修改","hidden":false},{"type":"ci","section":"CI | 更改我们的 CI 配置文件和脚本","hidden":false}]' - release-as: 0.5.0 \ No newline at end of file +# release-as: 0.5.0 \ No newline at end of file diff --git a/README.md b/README.md index ebfee5e..6befa61 100644 --- a/README.md +++ b/README.md @@ -187,46 +187,6 @@ func main() { ``` 在分布式环境中,如果存在类似于多服务器需要同时间刷新配置时,可使用`Cron`表达式设置定时任务。 -### 流操作 -可以通过 `stream` 包快速开启对`切片`和`map`的流式操作,例如: -```go -package main - -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/stream" - "github.com/kercylan98/minotaur/utils/streams" -) - -func main() { - s := stream.WithSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}). - 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 - }) - - fmt.Println(s) -} -``` - ### 基于`xlsx`文件的配置导出工具 该导出器的`xlsx`文件配置使用`JSON`语法进行复杂类型配置,具体可参考图例 - **[`planner/pce/exporter`](planner/pce/exporter)** 是实现了基于`xlsx`文件的配置导出工具,可直接编译成可执行文件使用; diff --git a/configuration/README.md b/configuration/README.md new file mode 100644 index 0000000..d3c23cc --- /dev/null +++ b/configuration/README.md @@ -0,0 +1,90 @@ +# Configuration + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +configuration 基于配置导表功能实现的配置加载及刷新功能 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Init](#Init)|配置初始化 +|[Load](#Load)|加载配置 +|[Refresh](#Refresh)|刷新配置 +|[WithTickerLoad](#WithTickerLoad)|通过定时器加载配置 +|[StopTickerLoad](#StopTickerLoad)|停止通过定时器加载配置 +|[RegConfigRefreshEvent](#RegConfigRefreshEvent)|当配置刷新时将立即执行被注册的事件处理函数 +|[OnConfigRefreshEvent](#OnConfigRefreshEvent)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[RefreshEventHandle](#refresheventhandle)|配置刷新事件处理函数 +|`INTERFACE`|[Loader](#loader)|配置加载器 + +
+ + +*** +## 详情信息 +#### func Init(loader ...Loader) + +> 配置初始化 +> - 在初始化后会立即加载配置 + +*** +#### func Load() + +> 加载配置 +> - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 + +*** +#### func Refresh() + +> 刷新配置 + +*** +#### func WithTickerLoad(ticker *timer.Ticker, interval time.Duration) + +> 通过定时器加载配置 +> - 通过定时器加载配置后,会自动刷新线上配置 +> - 调用该函数后不会立即刷新,而是在 interval 后加载并刷新一次配置,之后每隔 interval 加载并刷新一次配置 + +*** +#### func StopTickerLoad() + +> 停止通过定时器加载配置 + +*** +#### func RegConfigRefreshEvent(handle RefreshEventHandle) + +> 当配置刷新时将立即执行被注册的事件处理函数 + +*** +#### func OnConfigRefreshEvent() + + +*** +### RefreshEventHandle `STRUCT` +配置刷新事件处理函数 +```go +type RefreshEventHandle func() +``` +### Loader `INTERFACE` +配置加载器 +```go +type Loader interface { + Load() + Refresh() +} +``` diff --git a/examples/internal/deadlock-detect-server/README.md b/examples/internal/deadlock-detect-server/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/examples/internal/deadlock-detect-server/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/examples/internal/room-shunt-server/README.md b/examples/internal/room-shunt-server/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/examples/internal/room-shunt-server/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/examples/internal/simple-echo-websocket-server/README.md b/examples/internal/simple-echo-websocket-server/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/examples/internal/simple-echo-websocket-server/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/examples/internal/ticker-server/README.md b/examples/internal/ticker-server/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/examples/internal/ticker-server/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/examples/internal/ticker/README.md b/examples/internal/ticker/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/examples/internal/ticker/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/game/README.md b/game/README.md index 4e64c34..d3a935d 100644 --- a/game/README.md +++ b/game/README.md @@ -1,38 +1,13 @@ # Game -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/game) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -Game 包提供了游戏领域中常见的功能实现,例如活动、任务、战斗、房间等实现。 -开发者可以使用它来快速构建游戏中的常见功能,例如游戏活动、任务系统、多人房间等。 +game 目录下包含了各类通用的游戏玩法性内容,其中该目录主要为基础性内容,具体目录将对应不同的游戏功能性内容。 -> 目前还在考虑逐步将该包移除,未来项目结构可能会进行调整 -## package activity [`活动`](./activity) -提供了通用的活动设计,开发者可以使用它来设计和实现游戏中的活动机制。活动是游戏中重要的激励和玩法设计元素,它可以是一次性的,也可以是周期性的。活动系统框架将包括活动的创建、开启、关闭、奖励等功能,开发者可以根据游戏类型和风格,定制不同类型的活动,并设定相应的奖励机制,以增加游戏的可玩性和挑战性。 -活动整体的配置将通过可选项的方式进行配置。 + -> 在 `activity` 包中提供了以下 6 种事件: -> - 即将开始的活动事件 -> - 活动开始事件 -> - 活动结束事件 -> - 活动结束后延长展示开始事件 -> - 活动结束后延长展示结束事件 -> - 新的一天事件 -## package fight [`战斗`](./fight) -在 `fight` 中目前仅提供了回合制[`TurnBased`](./fight/turn_based.go)的实现。 -- [`TurnBased`](./fight/turn_based.go) 仅提供了回合制的基本实现,开发者可以根据自己的需求进行扩展。其中包括:回合切换、回合开始、回合结束、回合超时、根据速度调节下次行动间隔。 - -## package space [`空间`](./space) -计划提供游戏中常见的空间设计,例如房间、地图等。开发者可以使用它来快速构建游戏中的常见空间,例如多人房间、地图等。 -> 目前仅提供了房间的基本实现 - -- [`Room`](./space/room_manager.go) 提供了房间的基本实现,开发者可以根据自己的需求进行扩展。 - - 房间通过 [`RoomManager`](./space/room_manager.go) 进行管理,由 [`RoomController`](./space/room_controller.go) 进行控制; - - 实现了加入、退出、事件、查询、销毁、座位、带顺序座位号、密码、人数上限等常用的基础功能。 - -## package task [`任务`](./task) -提供了通用的任务设计,开发者可以使用它来设计和实现游戏中的任务机制。任务是游戏中重要的激励和玩法设计元素,它可以是一次性的,也可以是周期性的。任务系统框架将包括任务的创建、开启、关闭、奖励等功能,开发者可以根据游戏类型和风格,定制不同类型的任务,并设定相应的奖励机制,以增加游戏的可玩性和挑战性。 - -> 该包仅实现了任务基本状态的管理,例如任务类型、分配、事件、状态变化等,开发者可以根据自己的需求进行扩展。 \ No newline at end of file +*** diff --git a/game/activity/README.md b/game/activity/README.md index 8364a57..ec9848b 100644 --- a/game/activity/README.md +++ b/game/activity/README.md @@ -1,12 +1,294 @@ # Activity -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/game/activity) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -`activity` 是为不同类型的活动提供灵活的状态管理实现,支持活动的预告、开始、结束、延长展示等状态。 +activity 活动状态管理 -## 设计思路 -- 为不同类型的活动提供灵活的状态管理框架,支持活动的预告、开始、结束、延长展示等状态。 -- 支持事件驱动,根据活动状态变化和时间触发事件。 -- 允许活动循环,并支持配置延长展示时间。 -- 在多线程环境下使用互斥锁进行同步。 -- 使用反射处理不同类型的活动数据。 \ No newline at end of file + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[SetTicker](#SetTicker)|设置自定义定时器,该方法必须在使用活动系统前调用,且只能调用一次 +|[LoadGlobalData](#LoadGlobalData)|加载所有活动全局数据 +|[LoadEntityData](#LoadEntityData)|加载所有活动实体数据 +|[LoadOrRefreshActivity](#LoadOrRefreshActivity)|加载或刷新活动 +|[DefineNoneDataActivity](#DefineNoneDataActivity)|声明无数据的活动类型 +|[DefineGlobalDataActivity](#DefineGlobalDataActivity)|声明拥有全局数据的活动类型 +|[DefineEntityDataActivity](#DefineEntityDataActivity)|声明拥有实体数据的活动类型 +|[DefineGlobalAndEntityDataActivity](#DefineGlobalAndEntityDataActivity)|声明拥有全局数据和实体数据的活动类型 +|[RegUpcomingEvent](#RegUpcomingEvent)|注册即将开始的活动事件处理器 +|[OnUpcomingEvent](#OnUpcomingEvent)|即将开始的活动事件 +|[RegStartedEvent](#RegStartedEvent)|注册活动开始事件处理器 +|[OnStartedEvent](#OnStartedEvent)|活动开始事件 +|[RegEndedEvent](#RegEndedEvent)|注册活动结束事件处理器 +|[OnEndedEvent](#OnEndedEvent)|活动结束事件 +|[RegExtendedShowStartedEvent](#RegExtendedShowStartedEvent)|注册活动结束后延长展示开始事件处理器 +|[OnExtendedShowStartedEvent](#OnExtendedShowStartedEvent)|活动结束后延长展示开始事件 +|[RegExtendedShowEndedEvent](#RegExtendedShowEndedEvent)|注册活动结束后延长展示结束事件处理器 +|[OnExtendedShowEndedEvent](#OnExtendedShowEndedEvent)|活动结束后延长展示结束事件 +|[RegNewDayEvent](#RegNewDayEvent)|注册新的一天事件处理器 +|[OnNewDayEvent](#OnNewDayEvent)|新的一天事件 +|[NewOptions](#NewOptions)|创建活动选项 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Activity](#activity)|活动描述 +|`STRUCT`|[Controller](#controller)|活动控制器 +|`INTERFACE`|[BasicActivityController](#basicactivitycontroller)|暂无描述... +|`INTERFACE`|[NoneDataActivityController](#nonedataactivitycontroller)|无数据活动控制器 +|`INTERFACE`|[GlobalDataActivityController](#globaldataactivitycontroller)|全局数据活动控制器 +|`INTERFACE`|[EntityDataActivityController](#entitydataactivitycontroller)|实体数据活动控制器 +|`INTERFACE`|[GlobalAndEntityDataActivityController](#globalandentitydataactivitycontroller)|全局数据和实体数据活动控制器 +|`STRUCT`|[DataMeta](#datameta)|全局活动数据 +|`STRUCT`|[EntityDataMeta](#entitydatameta)|活动实体数据 +|`STRUCT`|[UpcomingEventHandler](#upcomingeventhandler)|暂无描述... +|`STRUCT`|[Options](#options)|活动选项 + +
+ + +*** +## 详情信息 +#### func SetTicker(size int, options ...timer.Option) + +> 设置自定义定时器,该方法必须在使用活动系统前调用,且只能调用一次 + +*** +#### func LoadGlobalData(handler func (activityType any)) + +> 加载所有活动全局数据 + +*** +#### func LoadEntityData(handler func (activityType any)) + +> 加载所有活动实体数据 + +*** +#### func LoadOrRefreshActivity(activityType Type, activityId ID, options ...*Options) error + +> 加载或刷新活动 +> - 通常在活动配置刷新时候将活动通过该方法注册或刷新 + +*** +#### func DefineNoneDataActivity(activityType Type) NoneDataActivityController[Type, ID, *none, none, *none] + +> 声明无数据的活动类型 + +*** +#### func DefineGlobalDataActivity(activityType Type) GlobalDataActivityController[Type, ID, Data, none, *none] + +> 声明拥有全局数据的活动类型 + +*** +#### func DefineEntityDataActivity(activityType Type) EntityDataActivityController[Type, ID, *none, EntityID, EntityData] + +> 声明拥有实体数据的活动类型 + +*** +#### func DefineGlobalAndEntityDataActivity(activityType Type) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] + +> 声明拥有全局数据和实体数据的活动类型 + +*** +#### func RegUpcomingEvent(activityType Type, handler UpcomingEventHandler[ID], priority ...int) + +> 注册即将开始的活动事件处理器 + +*** +#### func OnUpcomingEvent(activity *Activity[Type, ID]) + +> 即将开始的活动事件 + +*** +#### func RegStartedEvent(activityType Type, handler StartedEventHandler[ID], priority ...int) + +> 注册活动开始事件处理器 + +*** +#### func OnStartedEvent(activity *Activity[Type, ID]) + +> 活动开始事件 + +*** +#### func RegEndedEvent(activityType Type, handler EndedEventHandler[ID], priority ...int) + +> 注册活动结束事件处理器 + +*** +#### func OnEndedEvent(activity *Activity[Type, ID]) + +> 活动结束事件 + +*** +#### func RegExtendedShowStartedEvent(activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) + +> 注册活动结束后延长展示开始事件处理器 + +*** +#### func OnExtendedShowStartedEvent(activity *Activity[Type, ID]) + +> 活动结束后延长展示开始事件 + +*** +#### func RegExtendedShowEndedEvent(activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) + +> 注册活动结束后延长展示结束事件处理器 + +*** +#### func OnExtendedShowEndedEvent(activity *Activity[Type, ID]) + +> 活动结束后延长展示结束事件 + +*** +#### func RegNewDayEvent(activityType Type, handler NewDayEventHandler[ID], priority ...int) + +> 注册新的一天事件处理器 + +*** +#### func OnNewDayEvent(activity *Activity[Type, ID]) + +> 新的一天事件 + +*** +#### func NewOptions() *Options + +> 创建活动选项 + +*** +### Activity `STRUCT` +活动描述 +```go +type Activity[Type generic.Basic, ID generic.Basic] struct { + id ID + t Type + options *Options + state byte + lazy bool + tickerKey string + retention time.Duration + retentionKey string + mutex sync.RWMutex + getLastNewDayTime func() time.Time + setLastNewDayTime func(time.Time) + clearData func() + initializeData func() +} +``` +### Controller `STRUCT` +活动控制器 +```go +type Controller[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] struct { + t Type + activities map[ID]*Activity[Type, ID] + globalData map[ID]*DataMeta[Data] + entityData map[ID]map[EntityID]*EntityDataMeta[EntityData] + entityTof reflect.Type + globalInit func(activityId ID, data *DataMeta[Data]) + entityInit func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData]) + mutex sync.RWMutex +} +``` +### BasicActivityController `INTERFACE` + +```go +type BasicActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + IsOpen(activityId ID) bool + IsShow(activityId ID) bool + IsOpenOrShow(activityId ID) bool + Refresh(activityId ID) +} +``` +### NoneDataActivityController `INTERFACE` +无数据活动控制器 +```go +type NoneDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + InitializeNoneData(handler func(activityId ID, data *DataMeta[Data])) NoneDataActivityController[Type, ID, Data, EntityID, EntityData] +} +``` +### GlobalDataActivityController `INTERFACE` +全局数据活动控制器 +```go +type GlobalDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + GetGlobalData(activityId ID) Data + InitializeGlobalData(handler func(activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, EntityID, EntityData] +} +``` +### EntityDataActivityController `INTERFACE` +实体数据活动控制器 +```go +type EntityDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + GetEntityData(activityId ID, entityId EntityID) EntityData + InitializeEntityData(handler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, Data, EntityID, EntityData] +} +``` +### GlobalAndEntityDataActivityController `INTERFACE` +全局数据和实体数据活动控制器 +```go +type GlobalAndEntityDataActivityController[Type generic.Basic, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + GetGlobalData(activityId ID) Data + GetEntityData(activityId ID, entityId EntityID) EntityData + InitializeGlobalAndEntityData(handler func(activityId ID, data *DataMeta[Data]), entityHandler func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] +} +``` +### DataMeta `STRUCT` +全局活动数据 +```go +type DataMeta[Data any] struct { + once sync.Once + Data Data + LastNewDay time.Time +} +``` +### EntityDataMeta `STRUCT` +活动实体数据 +```go +type EntityDataMeta[Data any] struct { + once sync.Once + Data Data + LastNewDay time.Time +} +``` +### UpcomingEventHandler `STRUCT` + +```go +type UpcomingEventHandler[ID generic.Basic] func(activityId ID) +``` +### Options `STRUCT` +活动选项 +```go +type Options struct { + Tl *times.StateLine[byte] + Loop time.Duration +} +``` +#### func (*Options) WithUpcomingTime(t time.Time) *Options +> 设置活动预告时间 +*** +#### func (*Options) WithStartTime(t time.Time) *Options +> 设置活动开始时间 +*** +#### func (*Options) WithEndTime(t time.Time) *Options +> 设置活动结束时间 +*** +#### func (*Options) WithExtendedShowTime(t time.Time) *Options +> 设置延长展示时间 +*** +#### func (*Options) WithLoop(interval time.Duration) *Options +> 设置活动循环,时间间隔小于等于 0 表示不循环 +> - 当活动状态展示结束后,会根据该选项设置的时间间隔重新开始 +*** diff --git a/game/activity/internal/example/README.md b/game/activity/internal/example/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/game/activity/internal/example/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/game/activity/internal/example/activities/README.md b/game/activity/internal/example/activities/README.md new file mode 100644 index 0000000..8dc4c29 --- /dev/null +++ b/game/activity/internal/example/activities/README.md @@ -0,0 +1,15 @@ +# Activities + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/game/activity/internal/example/activities/demoactivity/README.md b/game/activity/internal/example/activities/demoactivity/README.md new file mode 100644 index 0000000..c6acce4 --- /dev/null +++ b/game/activity/internal/example/activities/demoactivity/README.md @@ -0,0 +1,15 @@ +# Demoactivity + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/game/activity/internal/example/types/README.md b/game/activity/internal/example/types/README.md new file mode 100644 index 0000000..a7d0d51 --- /dev/null +++ b/game/activity/internal/example/types/README.md @@ -0,0 +1,32 @@ +# Types + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[DemoActivityData](#demoactivitydata)|暂无描述... + +
+ + +*** +## 详情信息 +### DemoActivityData `STRUCT` + +```go +type DemoActivityData struct { + LoginNum int +} +``` diff --git a/game/fight/README.md b/game/fight/README.md index 9fa9859..2b977fc 100644 --- a/game/fight/README.md +++ b/game/fight/README.md @@ -1,18 +1,100 @@ # Fight -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/game/fight) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -## TurnBased [`回合制`]((https://pkg.go.dev/github.com/kercylan98/minotaur/game/fight#TurnBased)) +暂无介绍... -### 设计思路 -- 在每个回合中计算下一次行动时间间隔。然后,会根据当前行动时间间隔选择下一个行动实体。 -- 当选择到下一个行动实体后,进入行动阶段。在行动阶段中,会先触发 [`TurnBasedEntitySwitchEvent`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/fight#TurnBased.RegTurnBasedEntitySwitchEvent) 事件,然后开始计时。 -- 当计时结束时,如果实体还未完成行动,则会触发 [`TurnBasedEntityActionTimeoutEvent`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/fight#TurnBased.RegTurnBasedEntityActionTimeoutEvent) 事件。如果实体已经完成行动,则会触发 [`TurnBasedEntityActionFinishEvent`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/fight#TurnBased.RegTurnBasedEntityActionFinishEvent) 事件。 -- 当实体完成行动后,会触发 [`TurnBasedEntityActionSubmitEvent`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/fight#TurnBased.RegTurnBasedEntityActionSubmitEvent) 事件。 -回合制功能的设计思路主要考虑了以下几个方面: +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 -* 灵活性:`TurnBased` 类型提供了丰富的属性和方法,可以满足不同游戏的需要。 -* 可扩展性:`TurnBased` 类型还提供了 `AddCamp`、`GetCamp`、`SetActionTimeout` 等方法,可以根据需要扩展回合制功能。 -* 事件驱动:回合制功能使用事件驱动的方式来通知回合制状态变化。 +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewTurnBased](#NewTurnBased)|创建一个新的回合制 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[TurnBased](#turnbased)|回合制 +|`INTERFACE`|[TurnBasedControllerInfo](#turnbasedcontrollerinfo)|暂无描述... +|`INTERFACE`|[TurnBasedControllerAction](#turnbasedcontrolleraction)|暂无描述... +|`STRUCT`|[TurnBasedController](#turnbasedcontroller)|回合制控制器 +|`STRUCT`|[TurnBasedEntitySwitchEventHandler](#turnbasedentityswitcheventhandler)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewTurnBased(calcNextTurnDuration func ( Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] + +> 创建一个新的回合制 +> - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间 + +*** +### TurnBased `STRUCT` +回合制 +```go +type TurnBased[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct { + *turnBasedEvents[CampID, EntityID, Camp, Entity] + controller *TurnBasedController[CampID, EntityID, Camp, Entity] + ticker *time.Ticker + actionWaitTicker *time.Ticker + actioning bool + actionMutex sync.RWMutex + entities []Entity + campRel map[EntityID]Camp + calcNextTurnDuration func(Camp, Entity) time.Duration + actionTimeoutHandler func(Camp, Entity) time.Duration + signal chan signal + round int + currCamp Camp + currEntity Entity + currActionTimeout time.Duration + currStart time.Time + closeMutex sync.RWMutex + closed bool +} +``` +### TurnBasedControllerInfo `INTERFACE` + +```go +type TurnBasedControllerInfo[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface { + GetRound() int + GetCamp() Camp + GetEntity() Entity + GetActionTimeoutDuration() time.Duration + GetActionStartTime() time.Time + GetActionEndTime() time.Time + Stop() +} +``` +### TurnBasedControllerAction `INTERFACE` + +```go +type TurnBasedControllerAction[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface { + TurnBasedControllerInfo[CampID, EntityID, Camp, Entity] + Finish() + Refresh(duration time.Duration) time.Time +} +``` +### TurnBasedController `STRUCT` +回合制控制器 +```go +type TurnBasedController[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct { + tb *TurnBased[CampID, EntityID, Camp, Entity] +} +``` +### TurnBasedEntitySwitchEventHandler `STRUCT` + +```go +type TurnBasedEntitySwitchEventHandler[CampID comparable, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerAction[CampID, EntityID, Camp, Entity]) +``` diff --git a/game/space/README.md b/game/space/README.md index aea2cae..58e3d32 100644 --- a/game/space/README.md +++ b/game/space/README.md @@ -1,54 +1,95 @@ # Space -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -计划提供游戏中常见的空间设计,例如房间、地图等。开发者可以使用它来快速构建游戏中的常见空间,例如多人房间、地图等。 +space 游戏中常见的空间设计,例如房间、地图等 -## Room [`房间`]((https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomManager)) -房间在 `Minotaur` 中仅仅只是一个可以为任意可比较类型的 `ID`,当需要将现有或新设计的房间纳入 [`RoomManager`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomManager) 管理时,需要实现 [`Room`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomManager) 管理时,仅需要实现 [`generic.IdR`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/generic#IdR) 接口即可。 -该功能由 -[`RoomManager`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomManager)、 -[`RoomController`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomController) -组成。 +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 -当创建一个新的房间并纳入 [`RoomManager`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomManager) 管理后,将会得到一个 [`RoomController`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomController)。通过 [`RoomController`](https://pkg.go.dev/github.com/kercylan98/minotaur/game/space#RoomController) 可以对房间进行管理,例如:获取房间信息、加入房间、退出房间等。 -### 使用示例 +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewRoomManager](#NewRoomManager)|创建房间管理器 RoomManager 的实例 +|[NewRoomControllerOptions](#NewRoomControllerOptions)|创建房间控制器选项 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[RoomController](#roomcontroller)|对房间进行操作的控制器,由 RoomManager 接管后返回 +|`STRUCT`|[RoomManager](#roommanager)|房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作 +|`STRUCT`|[RoomAssumeControlEventHandle](#roomassumecontroleventhandle)|暂无描述... +|`STRUCT`|[RoomControllerOptions](#roomcontrolleroptions)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] + +> 创建房间管理器 RoomManager 的实例 + +示例代码: ```go -package main -import ( - "fmt" - "github.com/kercylan98/minotaur/game/space" -) - -type Room struct { - Id int64 -} - -func (r *Room) GetId() int64 { - return r.Id -} - -type Player struct { - Id string -} - -func (p *Player) GetId() string { - return p.Id -} - -func main() { +func ExampleNewRoomManager() { var rm = space.NewRoomManager[string, int64, *Player, *Room]() - var room = &Room{Id: 1} - var controller = rm.AssumeControl(room) - - if err := controller.AddEntity(&Player{Id: "1"}); err != nil { - // 房间密码不匹配或者房间已满 - panic(err) - } - - fmt.Println(controller.GetEntityCount()) // 1 + fmt.Println(rm == nil) } -``` \ No newline at end of file + +``` + +*** +#### func NewRoomControllerOptions() *RoomControllerOptions[EntityID, RoomID, Entity, Room] + +> 创建房间控制器选项 + +*** +### RoomController `STRUCT` +对房间进行操作的控制器,由 RoomManager 接管后返回 +```go +type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct { + manager *RoomManager[EntityID, RoomID, Entity, Room] + options *RoomControllerOptions[EntityID, RoomID, Entity, Room] + room Room + entities map[EntityID]Entity + entitiesRWMutex sync.RWMutex + vacancy []int + seat []*EntityID + owner *EntityID +} +``` +### RoomManager `STRUCT` +房间管理器是用于对房间进行管理的基本单元,通过该实例可以对房间进行增删改查等操作 + - 该实例是线程安全的 +```go +type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct { + *roomManagerEvents[EntityID, RoomID, Entity, Room] + roomsRWMutex sync.RWMutex + rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] +} +``` +### RoomAssumeControlEventHandle `STRUCT` + +```go +type RoomAssumeControlEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room]) +``` +### RoomControllerOptions `STRUCT` + +```go +type RoomControllerOptions[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct { + maxEntityCount *int + password *string + ownerInherit bool + ownerInheritHandler func(controller *RoomController[EntityID, RoomID, Entity, Room]) *EntityID +} +``` diff --git a/game/task/README.md b/game/task/README.md new file mode 100644 index 0000000..3d5d02f --- /dev/null +++ b/game/task/README.md @@ -0,0 +1,286 @@ +# Task + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Cond](#Cond)|创建任务条件 +|[RegisterRefreshTaskCounterEvent](#RegisterRefreshTaskCounterEvent)|注册特定任务类型的刷新任务计数器事件处理函数 +|[OnRefreshTaskCounterEvent](#OnRefreshTaskCounterEvent)|触发特定任务类型的刷新任务计数器事件 +|[RegisterRefreshTaskConditionEvent](#RegisterRefreshTaskConditionEvent)|注册特定任务类型的刷新任务条件事件处理函数 +|[OnRefreshTaskConditionEvent](#OnRefreshTaskConditionEvent)|触发特定任务类型的刷新任务条件事件 +|[WithType](#WithType)|设置任务类型 +|[WithCondition](#WithCondition)|设置任务完成条件,当满足条件时,任务状态为完成 +|[WithCounter](#WithCounter)|设置任务计数器,当计数器达到要求时,任务状态为完成 +|[WithOverflowCounter](#WithOverflowCounter)|设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成 +|[WithDeadline](#WithDeadline)|设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败 +|[WithLimitedDuration](#WithLimitedDuration)|设置任务限时,超过限时时间并且任务未完成时,任务状态为失败 +|[NewTask](#NewTask)|生成任务 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Condition](#condition)|任务条件 +|`STRUCT`|[RefreshTaskCounterEventHandler](#refreshtaskcountereventhandler)|暂无描述... +|`STRUCT`|[Option](#option)|任务选项 +|`STRUCT`|[Status](#status)|暂无描述... +|`STRUCT`|[Task](#task)|是对任务信息进行描述和处理的结构体 + +
+ + +*** +## 详情信息 +#### func Cond(k any, v any) Condition + +> 创建任务条件 + +
+查看 / 收起单元测试 + + +```go + +func TestCond(t *testing.T) { + task := NewTask(WithType("T"), WithCounter(5), WithCondition(Cond("N", 5).Cond("M", 10))) + task.AssignConditionValueAndRefresh("N", 5) + task.AssignConditionValueAndRefresh("M", 10) + RegisterRefreshTaskCounterEvent[*Player](task.Type, func(taskType string, trigger *Player, count int64) { + fmt.Println("Player", count) + for _, t := range trigger.tasks[taskType] { + fmt.Println(t.CurrCount, t.IncrementCounter(count).Status) + } + }) + RegisterRefreshTaskConditionEvent[*Player](task.Type, func(taskType string, trigger *Player, condition Condition) { + fmt.Println("Player", condition) + for _, t := range trigger.tasks[taskType] { + fmt.Println(t.CurrCount, t.AssignConditionValueAndRefresh("N", 5).Status) + } + }) + RegisterRefreshTaskCounterEvent[*Monster](task.Type, func(taskType string, trigger *Monster, count int64) { + fmt.Println("Monster", count) + }) + player := &Player{tasks: map[string][]*Task{task.Type: {task}}} + OnRefreshTaskCounterEvent(task.Type, player, 1) + OnRefreshTaskCounterEvent(task.Type, player, 2) + OnRefreshTaskCounterEvent(task.Type, player, 3) + OnRefreshTaskCounterEvent(task.Type, new(Monster), 3) +} + +``` + + +
+ + +*** +#### func RegisterRefreshTaskCounterEvent(taskType string, handler RefreshTaskCounterEventHandler[Trigger]) + +> 注册特定任务类型的刷新任务计数器事件处理函数 + +*** +#### func OnRefreshTaskCounterEvent(taskType string, trigger any, count int64) + +> 触发特定任务类型的刷新任务计数器事件 + +*** +#### func RegisterRefreshTaskConditionEvent(taskType string, handler RefreshTaskConditionEventHandler[Trigger]) + +> 注册特定任务类型的刷新任务条件事件处理函数 + +*** +#### func OnRefreshTaskConditionEvent(taskType string, trigger any, condition Condition) + +> 触发特定任务类型的刷新任务条件事件 + +*** +#### func WithType(taskType string) Option + +> 设置任务类型 + +*** +#### func WithCondition(condition Condition) Option + +> 设置任务完成条件,当满足条件时,任务状态为完成 +> - 任务条件值需要变更时可通过 Task.AssignConditionValueAndRefresh 方法变更 +> - 当多次设置该选项时,后面的设置会覆盖之前的设置 + +*** +#### func WithCounter(counter int64, initCount ...int64) Option + +> 设置任务计数器,当计数器达到要求时,任务状态为完成 +> - 一些场景下,任务计数器可能会溢出,此时可通过 WithOverflowCounter 设置可溢出的任务计数器 +> - 当多次设置该选项时,后面的设置会覆盖之前的设置 +> - 如果需要初始化计数器的值,可通过 initCount 参数设置 + +*** +#### func WithOverflowCounter(counter int64, initCount ...int64) Option + +> 设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成 +> - 当多次设置该选项时,后面的设置会覆盖之前的设置 +> - 如果需要初始化计数器的值,可通过 initCount 参数设置 + +*** +#### func WithDeadline(deadline time.Time) Option + +> 设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败 + +*** +#### func WithLimitedDuration(start time.Time, duration time.Duration) Option + +> 设置任务限时,超过限时时间并且任务未完成时,任务状态为失败 + +*** +#### func NewTask(options ...Option) *Task + +> 生成任务 + +*** +### Condition `STRUCT` +任务条件 +```go +type Condition map[any]any +``` +#### func (Condition) Cond(k any, v any) Condition +> 创建任务条件 +*** +#### func (Condition) GetString(key any) string +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetInt(key any) int +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetInt8(key any) int8 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetInt16(key any) int16 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetInt32(key any) int32 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetInt64(key any) int64 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetUint(key any) uint +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetUint8(key any) uint8 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetUint16(key any) uint16 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetUint32(key any) uint32 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetUint64(key any) uint64 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetFloat32(key any) float32 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetFloat64(key any) float64 +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetBool(key any) bool +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetTime(key any) time.Time +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetDuration(key any) time.Duration +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetByte(key any) byte +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetBytes(key any) []byte +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetRune(key any) rune +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetRunes(key any) []rune +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +#### func (Condition) GetAny(key any) any +> 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值 +*** +### RefreshTaskCounterEventHandler `STRUCT` + +```go +type RefreshTaskCounterEventHandler[Trigger any] func(taskType string, trigger Trigger, count int64) +``` +### Option `STRUCT` +任务选项 +```go +type Option func(task *Task) +``` +### Status `STRUCT` + +```go +type Status byte +``` +#### func (Status) String() string +*** +### Task `STRUCT` +是对任务信息进行描述和处理的结构体 +```go +type Task struct { + Type string + Status Status + Cond Condition + CondValue map[any]any + Counter int64 + CurrCount int64 + CurrOverflow bool + Deadline time.Time + StartTime time.Time + LimitedDuration time.Duration +} +``` +#### func (*Task) IsComplete() bool +> 判断任务是否已完成 +*** +#### func (*Task) IsFailed() bool +> 判断任务是否已失败 +*** +#### func (*Task) IsReward() bool +> 判断任务是否已领取奖励 +*** +#### func (*Task) ReceiveReward() bool +> 领取任务奖励,当任务状态为已完成时,才能领取奖励,此时返回 true,并且任务状态变更为已领取奖励 +*** +#### func (*Task) IncrementCounter(incr int64) *Task +> 增加计数器的值,当 incr 为负数时,计数器的值不会发生变化 +> - 如果需要溢出计数器,可通过 WithOverflowCounter 设置可溢出的任务计数器 +*** +#### func (*Task) DecrementCounter(decr int64) *Task +> 减少计数器的值,当 decr 为负数时,计数器的值不会发生变化 +*** +#### func (*Task) AssignConditionValueAndRefresh(key any, value any) *Task +> 分配条件值并刷新任务状态 +*** +#### func (*Task) AssignConditionValueAndRefreshByCondition(condition Condition) *Task +> 分配条件值并刷新任务状态 +*** +#### func (*Task) ResetStatus() *Task +> 重置任务状态 +> - 该函数会将任务状态重置为已接受状态后,再刷新任务状态 +> - 当任务条件变更,例如任务计数要求为 10,已经完成的情况下,将任务计数要求变更为 5 或 20,此时任务状态由于是已完成或已领取状态,不会自动刷新,需要调用该函数刷新任务状态 +*** diff --git a/notify/README.md b/notify/README.md new file mode 100644 index 0000000..284040b --- /dev/null +++ b/notify/README.md @@ -0,0 +1,68 @@ +# Notify + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +notify 包含了对外部第三方通知的实现,如机器人消息等 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewManager](#NewManager)|通过指定的 Sender 创建一个通知管理器, senders 包中提供了一些内置的 Sender + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Manager](#manager)|通知管理器,可用于将通知同时发送至多个渠道 +|`INTERFACE`|[Notify](#notify)|通用通知接口定义 +|`INTERFACE`|[Sender](#sender)|通知发送器接口声明 + +
+ + +*** +## 详情信息 +#### func NewManager(senders ...Sender) *Manager + +> 通过指定的 Sender 创建一个通知管理器, senders 包中提供了一些内置的 Sender + +*** +### Manager `STRUCT` +通知管理器,可用于将通知同时发送至多个渠道 +```go +type Manager struct { + senders []Sender + notifyChannel chan Notify + closeChannel chan struct{} +} +``` +#### func (*Manager) PushNotify(notify Notify) +> 推送通知 +*** +#### func (*Manager) Release() +> 释放通知管理器 +*** +### Notify `INTERFACE` +通用通知接口定义 +```go +type Notify interface { + Format() (string, error) +} +``` +### Sender `INTERFACE` +通知发送器接口声明 +```go +type Sender interface { + Push(notify Notify) error +} +``` diff --git a/notify/notifies/README.md b/notify/notifies/README.md new file mode 100644 index 0000000..7b99d6e --- /dev/null +++ b/notify/notifies/README.md @@ -0,0 +1,226 @@ +# Notifies + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +notifies 包含了内置通知内容的实现 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewFeiShu](#NewFeiShu)|创建飞书通知消息 +|[FeiShuMessageWithText](#FeiShuMessageWithText)|飞书文本消息 +|[FeiShuMessageWithRichText](#FeiShuMessageWithRichText)|飞书富文本消息 +|[FeiShuMessageWithImage](#FeiShuMessageWithImage)|飞书图片消息 +|[FeiShuMessageWithInteractive](#FeiShuMessageWithInteractive)|飞书卡片消息 +|[FeiShuMessageWithShareChat](#FeiShuMessageWithShareChat)|飞书分享群名片 +|[FeiShuMessageWithShareUser](#FeiShuMessageWithShareUser)|飞书分享个人名片 +|[FeiShuMessageWithAudio](#FeiShuMessageWithAudio)|飞书语音消息 +|[FeiShuMessageWithMedia](#FeiShuMessageWithMedia)|飞书视频消息 +|[FeiShuMessageWithMediaAndCover](#FeiShuMessageWithMediaAndCover)|飞书带封面的视频消息 +|[FeiShuMessageWithFile](#FeiShuMessageWithFile)|飞书文件消息 +|[FeiShuMessageWithSticker](#FeiShuMessageWithSticker)|飞书表情包消息 +|[NewFeiShuRichText](#NewFeiShuRichText)|创建一个飞书富文本 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[FeiShu](#feishu)|飞书通知消息 +|`STRUCT`|[FeiShuMessage](#feishumessage)|暂无描述... +|`STRUCT`|[FeiShuRichText](#feishurichtext)|飞书富文本结构 +|`STRUCT`|[FeiShuRichTextContent](#feishurichtextcontent)|飞书富文本内容体 + +
+ + +*** +## 详情信息 +#### func NewFeiShu(message FeiShuMessage) *FeiShu + +> 创建飞书通知消息 + +*** +#### func FeiShuMessageWithText(text string) FeiShuMessage + +> 飞书文本消息 +> - 支持通过换行符进行消息换行 +> - 支持通过 名字 进行@用户 +> - 支持通过 所有人 进行@所有人(必须满足所在群开启@所有人功能。) +> +> 支持加粗、斜体、下划线、删除线四种样式,可嵌套使用: +> - 加粗: 文本示例 +> - 斜体: 文本示例 +> - 下划线 : 文本示例 +> - 删除线: 文本示例 +> +> 超链接使用说明 +> - 超链接的使用格式为[文本](链接), 如[Feishu Open Platform](https://open.feishu.cn) 。 +> - 请确保链接是合法的,否则会以原始内容发送消息。 + +*** +#### func FeiShuMessageWithRichText(richText *FeiShuRichText) FeiShuMessage + +> 飞书富文本消息 + +*** +#### func FeiShuMessageWithImage(imageKey string) FeiShuMessage + +> 飞书图片消息 +> - imageKey 可通过上传图片接口获取 + +*** +#### func FeiShuMessageWithInteractive(json string) FeiShuMessage + +> 飞书卡片消息 +> - json 表示卡片的 json 数据或者消息模板的 json 数据 + +*** +#### func FeiShuMessageWithShareChat(chatId string) FeiShuMessage + +> 飞书分享群名片 +> - chatId 群ID获取方式请参见群ID说明 +> +> 群ID说明:https://open.feishu.cn/document/server-docs/group/chat/chat-id-description + +*** +#### func FeiShuMessageWithShareUser(userId string) FeiShuMessage + +> 飞书分享个人名片 +> - userId 表示用户的 OpenID 获取方式请参见了解更多:如何获取 Open ID +> +> 如何获取 Open ID:https://open.feishu.cn/document/faq/trouble-shooting/how-to-obtain-openid + +*** +#### func FeiShuMessageWithAudio(fileKey string) FeiShuMessage + +> 飞书语音消息 +> - fileKey 语音文件Key,可通过上传文件接口获取 +> +> 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create + +*** +#### func FeiShuMessageWithMedia(fileKey string) FeiShuMessage + +> 飞书视频消息 +> - fileKey 视频文件Key,可通过上传文件接口获取 +> +> 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create + +*** +#### func FeiShuMessageWithMediaAndCover(fileKey string, imageKey string) FeiShuMessage + +> 飞书带封面的视频消息 +> - fileKey 视频文件Key,可通过上传文件接口获取 +> - imageKey 图片文件Key,可通过上传文件接口获取 +> +> 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create + +*** +#### func FeiShuMessageWithFile(fileKey string) FeiShuMessage + +> 飞书文件消息 +> - fileKey 文件Key,可通过上传文件接口获取 +> +> 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create + +*** +#### func FeiShuMessageWithSticker(fileKey string) FeiShuMessage + +> 飞书表情包消息 +> - fileKey 表情包文件Key,目前仅支持发送机器人收到的表情包,可通过接收消息事件的推送获取表情包 file_key。 +> +> 接收消息事件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/events/receive + +*** +#### func NewFeiShuRichText() *FeiShuRichText + +> 创建一个飞书富文本 + +*** +### FeiShu `STRUCT` +飞书通知消息 +```go +type FeiShu struct { + Content any + MsgType string +} +``` +#### func (*FeiShu) Format() string, error +> 格式化通知内容 +*** +### FeiShuMessage `STRUCT` + +```go +type FeiShuMessage func(feishu *FeiShu) +``` +### FeiShuRichText `STRUCT` +飞书富文本结构 +```go +type FeiShuRichText struct { + content map[string]*FeiShuRichTextContent +} +``` +#### func (*FeiShuRichText) Create(lang string, title string) *FeiShuRichTextContent +> 创建一个特定语言和标题的富文本内容 +*** +### FeiShuRichTextContent `STRUCT` +飞书富文本内容体 +```go +type FeiShuRichTextContent struct { + richText *FeiShuRichText + Title string + Content [][]map[string]any +} +``` +#### func (*FeiShuRichTextContent) AddText(text string, styles ...string) *FeiShuRichTextContent +> 添加文本 +*** +#### func (*FeiShuRichTextContent) AddUnescapeText(text string, styles ...string) *FeiShuRichTextContent +> 添加 unescape 解码的文本 +*** +#### func (*FeiShuRichTextContent) AddLink(text string, href string, styles ...string) *FeiShuRichTextContent +> 添加超链接文本 +> - 请确保链接地址的合法性,否则消息会发送失败 +*** +#### func (*FeiShuRichTextContent) AddAt(userId string, styles ...string) *FeiShuRichTextContent +> 添加@的用户 +> - @单个用户时,userId 字段必须是有效值 +> - @所有人填"all"。 +*** +#### func (*FeiShuRichTextContent) AddAtWithUsername(userId string, username string, styles ...string) *FeiShuRichTextContent +> 添加包含用户名的@用户 +> - @单个用户时,userId 字段必须是有效值 +> - @所有人填"all"。 +*** +#### func (*FeiShuRichTextContent) AddImg(imageKey string) *FeiShuRichTextContent +> 添加图片 +> - imageKey 表示图片的唯一标识,可通过上传图片接口获取 +*** +#### func (*FeiShuRichTextContent) AddMedia(fileKey string) *FeiShuRichTextContent +> 添加视频 +> - fileKey 表示视频文件的唯一标识,可通过上传文件接口获取 +*** +#### func (*FeiShuRichTextContent) AddMediaWithCover(fileKey string, imageKey string) *FeiShuRichTextContent +> 添加包含封面的视频 +> - fileKey 表示视频文件的唯一标识,可通过上传文件接口获取 +> - imageKey 表示图片的唯一标识,可通过上传图片接口获取 +*** +#### func (*FeiShuRichTextContent) AddEmotion(emojiType string) *FeiShuRichTextContent +> 添加表情 +> - emojiType 表示表情类型,部分可选值请参见表情文案。 +> +> 表情文案:https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce +*** +#### func (*FeiShuRichTextContent) Ok() *FeiShuRichText +> 确认完成,将返回 FeiShuRichText 可继续创建多语言富文本 +*** diff --git a/notify/senders/README.md b/notify/senders/README.md new file mode 100644 index 0000000..a446930 --- /dev/null +++ b/notify/senders/README.md @@ -0,0 +1,68 @@ +# Senders + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +senders Package 包含了内置通知发送器的实现 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewFeiShu](#NewFeiShu)|根据特定的 webhook 地址创建飞书发送器 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[FeiShu](#feishu)|飞书发送器 + +
+ + +*** +## 详情信息 +#### func NewFeiShu(webhook string) *FeiShu + +> 根据特定的 webhook 地址创建飞书发送器 + +*** +### FeiShu `STRUCT` +飞书发送器 +```go +type FeiShu struct { + client *resty.Client + webhook string +} +``` +#### func (*FeiShu) Push(notify notify.Notify) error +> 推送通知 +
+查看 / 收起单元测试 + + +```go + +func TestFeiShu_Push(t *testing.T) { + fs := NewFeiShu("https://open.feishu.cn/open-apis/bot/v2/hook/d886f30f-814c-47b1-aeb0-b508da0f7f22") + rt := notifies.NewFeiShu(notifies.FeiShuMessageWithRichText(notifies.NewFeiShuRichText().Create("zh_cn", "标题咯").AddText("哈哈哈").Ok())) + if err := fs.Push(rt); err != nil { + panic(err) + } +} + +``` + + +
+ + +*** diff --git a/planner/README.md b/planner/README.md new file mode 100644 index 0000000..7cab4ea --- /dev/null +++ b/planner/README.md @@ -0,0 +1,13 @@ +# Planner + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +planner 包含了策划工具相关的内容 + + + + + + +*** diff --git a/planner/pce/README.md b/planner/pce/README.md new file mode 100644 index 0000000..dd10bdd --- /dev/null +++ b/planner/pce/README.md @@ -0,0 +1,520 @@ +# Pce + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewExporter](#NewExporter)|创建导出器 +|[GetFieldGolangType](#GetFieldGolangType)|获取字段的 Golang 类型 +|[GetFields](#GetFields)|获取所有内置支持的字段 +|[NewLoader](#NewLoader)|创建加载器 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`INTERFACE`|[Config](#config)|配置解析接口 +|`INTERFACE`|[DataTmpl](#datatmpl)|数据导出模板 +|`STRUCT`|[Exporter](#exporter)|导出器 +|`INTERFACE`|[Field](#field)|基本字段类型接口 +|`STRUCT`|[Int](#int)|暂无描述... +|`STRUCT`|[Int8](#int8)|暂无描述... +|`STRUCT`|[Int16](#int16)|暂无描述... +|`STRUCT`|[Int32](#int32)|暂无描述... +|`STRUCT`|[Int64](#int64)|暂无描述... +|`STRUCT`|[Uint](#uint)|暂无描述... +|`STRUCT`|[Uint8](#uint8)|暂无描述... +|`STRUCT`|[Uint16](#uint16)|暂无描述... +|`STRUCT`|[Uint32](#uint32)|暂无描述... +|`STRUCT`|[Uint64](#uint64)|暂无描述... +|`STRUCT`|[Float32](#float32)|暂无描述... +|`STRUCT`|[Float64](#float64)|暂无描述... +|`STRUCT`|[String](#string)|暂无描述... +|`STRUCT`|[Bool](#bool)|暂无描述... +|`STRUCT`|[Byte](#byte)|暂无描述... +|`STRUCT`|[Rune](#rune)|暂无描述... +|`STRUCT`|[Complex64](#complex64)|暂无描述... +|`STRUCT`|[Complex128](#complex128)|暂无描述... +|`STRUCT`|[Uintptr](#uintptr)|暂无描述... +|`STRUCT`|[Double](#double)|暂无描述... +|`STRUCT`|[Float](#float)|暂无描述... +|`STRUCT`|[Long](#long)|暂无描述... +|`STRUCT`|[Short](#short)|暂无描述... +|`STRUCT`|[Char](#char)|暂无描述... +|`STRUCT`|[Number](#number)|暂无描述... +|`STRUCT`|[Integer](#integer)|暂无描述... +|`STRUCT`|[Boolean](#boolean)|暂无描述... +|`STRUCT`|[Loader](#loader)|配置加载器 +|`STRUCT`|[DataInfo](#datainfo)|配置数据 +|`STRUCT`|[DataField](#datafield)|配置数据字段 +|`INTERFACE`|[Tmpl](#tmpl)|配置结构模板接口 +|`STRUCT`|[TmplField](#tmplfield)|模板字段 +|`STRUCT`|[TmplStruct](#tmplstruct)|模板结构 + +
+ + +*** +## 详情信息 +#### func NewExporter() *Exporter + +> 创建导出器 + +*** +#### func GetFieldGolangType(field Field) string + +> 获取字段的 Golang 类型 + +
+查看 / 收起单元测试 + + +```go + +func TestGetFieldGolangType(t *testing.T) { + fmt.Println(pce.GetFieldGolangType(new(pce.String))) +} + +``` + + +
+ + +*** +#### func GetFields() []Field + +> 获取所有内置支持的字段 + +*** +#### func NewLoader(fields []Field) *Loader + +> 创建加载器 +> - 加载器被用于加载配置表的数据和结构信息 + +*** +### Config `INTERFACE` +配置解析接口 + - 用于将配置文件解析为可供分析的数据结构 + - 可以在 cs 包中找到内置提供的实现及其模板,例如 cs.XlsxIndexConfig +```go +type Config interface { + GetConfigName() string + GetDisplayName() string + GetDescription() string + GetIndexCount() int + GetFields() []DataField + GetData() [][]DataInfo +} +``` +### DataTmpl `INTERFACE` +数据导出模板 +```go +type DataTmpl interface { + Render(data map[any]any) (string, error) +} +``` +### Exporter `STRUCT` +导出器 +```go +type Exporter struct{} +``` +#### func (*Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) []byte, error +> 导出结构 +*** +#### func (*Exporter) ExportData(tmpl DataTmpl, data map[any]any) []byte, error +> 导出数据 +*** +### Field `INTERFACE` +基本字段类型接口 +```go +type Field interface { + TypeName() string + Zero() any + Parse(value string) any +} +``` +### Int `STRUCT` + +```go +type Int int +``` +#### func (Int) TypeName() string +*** +#### func (Int) Zero() any +*** +#### func (Int) Parse(value string) any +*** +### Int8 `STRUCT` + +```go +type Int8 int8 +``` +#### func (Int8) TypeName() string +*** +#### func (Int8) Zero() any +*** +#### func (Int8) Parse(value string) any +*** +### Int16 `STRUCT` + +```go +type Int16 int16 +``` +#### func (Int16) TypeName() string +*** +#### func (Int16) Zero() any +*** +#### func (Int16) Parse(value string) any +*** +### Int32 `STRUCT` + +```go +type Int32 int32 +``` +#### func (Int32) TypeName() string +*** +#### func (Int32) Zero() any +*** +#### func (Int32) Parse(value string) any +*** +### Int64 `STRUCT` + +```go +type Int64 int64 +``` +#### func (Int64) TypeName() string +*** +#### func (Int64) Zero() any +*** +#### func (Int64) Parse(value string) any +*** +### Uint `STRUCT` + +```go +type Uint uint +``` +#### func (Uint) TypeName() string +*** +#### func (Uint) Zero() any +*** +#### func (Uint) Parse(value string) any +*** +### Uint8 `STRUCT` + +```go +type Uint8 uint8 +``` +#### func (Uint8) TypeName() string +*** +#### func (Uint8) Zero() any +*** +#### func (Uint8) Parse(value string) any +*** +### Uint16 `STRUCT` + +```go +type Uint16 uint16 +``` +#### func (Uint16) TypeName() string +*** +#### func (Uint16) Zero() any +*** +#### func (Uint16) Parse(value string) any +*** +### Uint32 `STRUCT` + +```go +type Uint32 uint32 +``` +#### func (Uint32) TypeName() string +*** +#### func (Uint32) Zero() any +*** +#### func (Uint32) Parse(value string) any +*** +### Uint64 `STRUCT` + +```go +type Uint64 uint64 +``` +#### func (Uint64) TypeName() string +*** +#### func (Uint64) Zero() any +*** +#### func (Uint64) Parse(value string) any +*** +### Float32 `STRUCT` + +```go +type Float32 float32 +``` +#### func (Float32) TypeName() string +*** +#### func (Float32) Zero() any +*** +#### func (Float32) Parse(value string) any +*** +### Float64 `STRUCT` + +```go +type Float64 float64 +``` +#### func (Float64) TypeName() string +*** +#### func (Float64) Zero() any +*** +#### func (Float64) Parse(value string) any +*** +### String `STRUCT` + +```go +type String string +``` +#### func (String) TypeName() string +*** +#### func (String) Zero() any +*** +#### func (String) Parse(value string) any +*** +### Bool `STRUCT` + +```go +type Bool bool +``` +#### func (Bool) TypeName() string +*** +#### func (Bool) Zero() any +*** +#### func (Bool) Parse(value string) any +*** +### Byte `STRUCT` + +```go +type Byte byte +``` +#### func (Byte) TypeName() string +*** +#### func (Byte) Zero() any +*** +#### func (Byte) Parse(value string) any +*** +### Rune `STRUCT` + +```go +type Rune rune +``` +#### func (Rune) TypeName() string +*** +#### func (Rune) Zero() any +*** +#### func (Rune) Parse(value string) any +*** +### Complex64 `STRUCT` + +```go +type Complex64 complex64 +``` +#### func (Complex64) TypeName() string +*** +#### func (Complex64) Zero() any +*** +#### func (Complex64) Parse(value string) any +*** +### Complex128 `STRUCT` + +```go +type Complex128 complex128 +``` +#### func (Complex128) TypeName() string +*** +#### func (Complex128) Zero() any +*** +#### func (Complex128) Parse(value string) any +*** +### Uintptr `STRUCT` + +```go +type Uintptr uintptr +``` +#### func (Uintptr) TypeName() string +*** +#### func (Uintptr) Zero() any +*** +#### func (Uintptr) Parse(value string) any +*** +### Double `STRUCT` + +```go +type Double float64 +``` +#### func (Double) TypeName() string +*** +#### func (Double) Zero() any +*** +#### func (Double) Parse(value string) any +*** +### Float `STRUCT` + +```go +type Float float32 +``` +#### func (Float) TypeName() string +*** +#### func (Float) Zero() any +*** +#### func (Float) Parse(value string) any +*** +### Long `STRUCT` + +```go +type Long int64 +``` +#### func (Long) TypeName() string +*** +#### func (Long) Zero() any +*** +#### func (Long) Parse(value string) any +*** +### Short `STRUCT` + +```go +type Short int16 +``` +#### func (Short) TypeName() string +*** +#### func (Short) Zero() any +*** +#### func (Short) Parse(value string) any +*** +### Char `STRUCT` + +```go +type Char int8 +``` +#### func (Char) TypeName() string +*** +#### func (Char) Zero() any +*** +#### func (Char) Parse(value string) any +*** +### Number `STRUCT` + +```go +type Number float64 +``` +#### func (Number) TypeName() string +*** +#### func (Number) Zero() any +*** +#### func (Number) Parse(value string) any +*** +### Integer `STRUCT` + +```go +type Integer int64 +``` +#### func (Integer) TypeName() string +*** +#### func (Integer) Zero() any +*** +#### func (Integer) Parse(value string) any +*** +### Boolean `STRUCT` + +```go +type Boolean bool +``` +#### func (Boolean) TypeName() string +*** +#### func (Boolean) Zero() any +*** +#### func (Boolean) Parse(value string) any +*** +### Loader `STRUCT` +配置加载器 +```go +type Loader struct { + fields map[string]Field +} +``` +#### func (*Loader) LoadStruct(config Config) *TmplStruct +> 加载结构 +*** +#### func (*Loader) LoadData(config Config) map[any]any +> 加载配置并得到配置数据 +*** +### DataInfo `STRUCT` +配置数据 +```go +type DataInfo struct { + DataField + Value string +} +``` +### DataField `STRUCT` +配置数据字段 +```go +type DataField struct { + Index int + Name string + Desc string + Type string + ExportType string +} +``` +### Tmpl `INTERFACE` +配置结构模板接口 +```go +type Tmpl interface { + Render(templates ...*TmplStruct) (string, error) +} +``` +### TmplField `STRUCT` +模板字段 +```go +type TmplField struct { + Name string + Desc string + Type string + Struct *TmplStruct + Index int + slice bool + isIndex bool +} +``` +#### func (*TmplField) IsIndex() bool +> 是否是索引字段 +*** +#### func (*TmplField) IsStruct() bool +> 是否是结构类型 +*** +#### func (*TmplField) IsSlice() bool +> 是否是切片类型 +*** +### TmplStruct `STRUCT` +模板结构 +```go +type TmplStruct struct { + Name string + Desc string + Fields []*TmplField + IndexCount int +} +``` +#### func (*TmplStruct) AllChildren() []*TmplStruct +> 获取所有子结构 +*** diff --git a/planner/pce/cs/README.md b/planner/pce/cs/README.md new file mode 100644 index 0000000..87e0cde --- /dev/null +++ b/planner/pce/cs/README.md @@ -0,0 +1,62 @@ +# Cs + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewXlsx](#NewXlsx)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[XlsxExportType](#xlsxexporttype)|暂无描述... +|`STRUCT`|[Xlsx](#xlsx)|内置的 Xlsx 配置 + +
+ + +*** +## 详情信息 +#### func NewXlsx(sheet *xlsx.Sheet, exportType XlsxExportType) *Xlsx + + +*** +### XlsxExportType `STRUCT` + +```go +type XlsxExportType int +``` +### Xlsx `STRUCT` +内置的 Xlsx 配置 +```go +type Xlsx struct { + sheet *xlsx.Sheet + exportType XlsxExportType +} +``` +#### func (*Xlsx) GetConfigName() string +*** +#### func (*Xlsx) GetDisplayName() string +*** +#### func (*Xlsx) GetDescription() string +*** +#### func (*Xlsx) GetIndexCount() int +*** +#### func (*Xlsx) GetFields() []pce.DataField +*** +#### func (*Xlsx) GetData() [][]pce.DataInfo +*** diff --git a/planner/pce/exporter/README.md b/planner/pce/exporter/README.md new file mode 100644 index 0000000..a526d73 --- /dev/null +++ b/planner/pce/exporter/README.md @@ -0,0 +1,15 @@ +# Main + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + + + + + + + +*** diff --git a/planner/pce/exporter/cmd/README.md b/planner/pce/exporter/cmd/README.md new file mode 100644 index 0000000..e9451ca --- /dev/null +++ b/planner/pce/exporter/cmd/README.md @@ -0,0 +1,117 @@ +# Cmd + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Execute](#Execute)|将所有子命令添加到根命令并适当设置标志。这是由 main.main() 调用的。 rootCmd 只需要发生一次 + + + +
+ + +*** +## 详情信息 +#### func Execute() + +> 将所有子命令添加到根命令并适当设置标志。这是由 main.main() 调用的。 rootCmd 只需要发生一次 + +
+查看 / 收起单元测试 + + +```go + +func TestExecute(t *testing.T) { + var filePath, outPath, exclude, exportType, prefix string + exportType = "s" + filePath = `.\游戏配置.xlsx` + filePath = `../xlsx_template.xlsx` + outPath = `.` + isDir, err := file.IsDir(outPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + isDir = filepath.Ext(outPath) == "" + } else { + panic(err) + } + } + if !isDir { + panic(errors.New("output must be a directory path")) + } + _ = os.MkdirAll(outPath, os.ModePerm) + fpd, err := file.IsDir(filePath) + if err != nil { + panic(err) + } + var xlsxFiles []string + if fpd { + files, err := os.ReadDir(filePath) + if err != nil { + panic(err) + } + for _, f := range files { + if f.IsDir() || !strings.HasSuffix(f.Name(), ".xlsx") || strings.HasPrefix(f.Name(), "~") { + continue + } + xlsxFiles = append(xlsxFiles, filepath.Join(filePath, f.Name())) + } + } else { + xlsxFiles = append(xlsxFiles, filePath) + } + var exporter = pce.NewExporter() + loader := pce.NewLoader(pce.GetFields()) + excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ",")) + for _, xlsxFile := range xlsxFiles { + xf, err := xlsx.OpenFile(xlsxFile) + if err != nil { + panic(err) + } + for _, sheet := range xf.Sheets { + var cx *cs.Xlsx + switch strings.TrimSpace(strings.ToLower(exportType)) { + case "c": + cx = cs.NewXlsx(sheet, cs.XlsxExportTypeClient) + case "s": + cx = cs.NewXlsx(sheet, cs.XlsxExportTypeServer) + } + if strings.HasPrefix(cx.GetDisplayName(), "#") || strings.HasPrefix(cx.GetConfigName(), "#") || excludes[cx.GetConfigName()] || excludes[cx.GetDisplayName()] { + continue + } + if raw, err := exporter.ExportData(tmpls.NewJSON(), loader.LoadData(cx)); err != nil { + panic(err) + } else { + var jsonPath string + if len(prefix) == 0 { + jsonPath = filepath.Join(outPath, fmt.Sprintf("%s.json", cx.GetConfigName())) + } else { + jsonPath = filepath.Join(outPath, fmt.Sprintf("%s.%s.json", prefix, cx.GetConfigName())) + } + if err := file.WriterFile(jsonPath, raw); err != nil { + panic(err) + } + } + } + } +} + +``` + + +
+ + +*** diff --git a/planner/pce/tmpls/README.md b/planner/pce/tmpls/README.md new file mode 100644 index 0000000..e0b1bb7 --- /dev/null +++ b/planner/pce/tmpls/README.md @@ -0,0 +1,68 @@ +# Tmpls + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewGolang](#NewGolang)|创建一个 Golang 配置导出模板 +|[NewJSON](#NewJSON)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Golang](#golang)|配置导出模板 +|`STRUCT`|[JSON](#json)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewGolang(packageName string) *Golang + +> 创建一个 Golang 配置导出模板 + +*** +#### func NewJSON() *JSON + + +*** +### Golang `STRUCT` +配置导出模板 +```go +type Golang struct { + Package string + Templates []*pce.TmplStruct +} +``` +#### func (*Golang) Render(templates ...*pce.TmplStruct) string, error +*** +#### func (*Golang) GetVariable(config *pce.TmplStruct) string +*** +#### func (*Golang) GetConfigName(config *pce.TmplStruct) string +*** +#### func (*Golang) HasIndex(config *pce.TmplStruct) bool +*** +### JSON `STRUCT` + +```go +type JSON struct { + jsonIter.API +} +``` +#### func (*JSON) Render(data map[any]any) string, error +*** diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..a79cad6 --- /dev/null +++ b/server/README.md @@ -0,0 +1,975 @@ +# Server + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +server 提供了包含多种网络类型的服务器实现 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewBot](#NewBot)|创建一个机器人,目前仅支持 Socket 服务器 +|[WithBotNetworkDelay](#WithBotNetworkDelay)|设置机器人网络延迟及波动范围 +|[WithBotWriter](#WithBotWriter)|设置机器人写入器,默认为 os.Stdout +|[DefaultWebsocketUpgrader](#DefaultWebsocketUpgrader)|暂无描述... +|[NewHttpHandleWrapper](#NewHttpHandleWrapper)|创建一个新的 http 处理程序包装器 +|[NewHttpContext](#NewHttpContext)|基于 gin.Context 创建一个新的 HttpContext +|[NewGinWrapper](#NewGinWrapper)|创建 gin 包装器,用于对 NewHttpWrapper 函数的替代 +|[HasMessageType](#HasMessageType)|检查是否存在指定的消息类型 +|[NewMultipleServer](#NewMultipleServer)|暂无描述... +|[GetNetworks](#GetNetworks)|获取所有支持的网络模式 +|[WithLowMessageDuration](#WithLowMessageDuration)|通过指定慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志 +|[WithAsyncLowMessageDuration](#WithAsyncLowMessageDuration)|通过指定异步消息的慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志 +|[WithWebsocketConnInitializer](#WithWebsocketConnInitializer)|通过 websocket 连接初始化的方式创建服务器,当 initializer 返回错误时,服务器将不会处理该连接的后续逻辑 +|[WithWebsocketUpgrade](#WithWebsocketUpgrade)|通过指定 websocket.Upgrader 的方式创建服务器 +|[WithConnWriteBufferSize](#WithConnWriteBufferSize)|通过连接写入缓冲区大小的方式创建服务器 +|[WithDispatcherBufferSize](#WithDispatcherBufferSize)|通过消息分发器缓冲区大小的方式创建服务器 +|[WithMessageStatistics](#WithMessageStatistics)|通过消息统计的方式创建服务器 +|[WithPacketWarnSize](#WithPacketWarnSize)|通过数据包大小警告的方式创建服务器,当数据包大小超过指定大小时,将会输出 WARN 类型的日志 +|[WithLimitLife](#WithLimitLife)|通过限制最大生命周期的方式创建服务器 +|[WithWebsocketWriteCompression](#WithWebsocketWriteCompression)|通过数据写入压缩的方式创建Websocket服务器 +|[WithWebsocketCompression](#WithWebsocketCompression)|通过数据压缩的方式创建Websocket服务器 +|[WithDeadlockDetect](#WithDeadlockDetect)|通过死锁、死循环、永久阻塞检测的方式创建服务器 +|[WithDisableAsyncMessage](#WithDisableAsyncMessage)|通过禁用异步消息的方式创建服务器 +|[WithAsyncPoolSize](#WithAsyncPoolSize)|通过指定异步消息池大小的方式创建服务器 +|[WithWebsocketReadDeadline](#WithWebsocketReadDeadline)|设置 Websocket 读取超时时间 +|[WithTicker](#WithTicker)|通过定时器创建服务器,为服务器添加定时器功能 +|[WithTLS](#WithTLS)|通过安全传输层协议TLS创建服务器 +|[WithGRPCServerOptions](#WithGRPCServerOptions)|通过GRPC的可选项创建GRPC服务器 +|[WithWebsocketMessageType](#WithWebsocketMessageType)|设置仅支持特定类型的Websocket消息 +|[WithPProf](#WithPProf)|通过性能分析工具PProf创建服务器 +|[New](#New)|根据特定网络类型创建一个服务器 +|[BindService](#BindService)|绑定服务到特定 Server,被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Bot](#bot)|暂无描述... +|`STRUCT`|[BotOption](#botoption)|暂无描述... +|`STRUCT`|[Conn](#conn)|服务器连接单次消息的包装 +|`STRUCT`|[ConsoleParams](#consoleparams)|控制台参数 +|`STRUCT`|[MessageReadyEventHandler](#messagereadyeventhandler)|暂无描述... +|`STRUCT`|[Http](#http)|基于 gin.Engine 包装的 http 服务器 +|`STRUCT`|[HttpContext](#httpcontext)|基于 gin.Context 的 http 请求上下文 +|`STRUCT`|[HandlerFunc](#handlerfunc)|暂无描述... +|`STRUCT`|[ContextPacker](#contextpacker)|暂无描述... +|`STRUCT`|[HttpRouter](#httprouter)|暂无描述... +|`STRUCT`|[HttpWrapperHandleFunc](#httpwrapperhandlefunc)|暂无描述... +|`STRUCT`|[HttpWrapper](#httpwrapper)|http 包装器 +|`STRUCT`|[HttpWrapperGroup](#httpwrappergroup)|http 包装器 +|`STRUCT`|[MessageType](#messagetype)|暂无描述... +|`STRUCT`|[Message](#message)|服务器消息 +|`STRUCT`|[MultipleServer](#multipleserver)|暂无描述... +|`STRUCT`|[Network](#network)|暂无描述... +|`STRUCT`|[Option](#option)|暂无描述... +|`STRUCT`|[Server](#server)|网络服务器 +|`INTERFACE`|[Service](#service)|兼容传统 service 设计模式的接口 + +
+ + +*** +## 详情信息 +#### func NewBot(srv *Server, options ...BotOption) *Bot + +> 创建一个机器人,目前仅支持 Socket 服务器 + +
+查看 / 收起单元测试 + + +```go + +func TestNewBot(t *testing.T) { + srv := server.New(server.NetworkWebsocket) + srv.RegConnectionOpenedEvent(func(srv *server.Server, conn *server.Conn) { + t.Logf("connection opened: %s", conn.GetID()) + conn.Close() + conn.Write([]byte("hello")) + }) + srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { + t.Logf("connection closed: %s", conn.GetID()) + }) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { + t.Logf("connection %s receive packet: %s", conn.GetID(), string(packet)) + conn.Write([]byte("world")) + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + bot := server.NewBot(srv, server.WithBotNetworkDelay(100, 20), server.WithBotWriter(func(bot *server.Bot) io.Writer { + return &Writer{t: t, bot: bot} + })) + bot.JoinServer() + time.Sleep(time.Second) + bot.SendPacket([]byte("hello")) + }) + _ = srv.Run(":9600") +} + +``` + + +
+ + +*** +#### func WithBotNetworkDelay(delay time.Duration, fluctuation time.Duration) BotOption + +> 设置机器人网络延迟及波动范围 +> - delay 延迟 +> - fluctuation 波动范围 + +*** +#### func WithBotWriter(construction func (bot *Bot) io.Writer) BotOption + +> 设置机器人写入器,默认为 os.Stdout + +*** +#### func DefaultWebsocketUpgrader() *websocket.Upgrader + + +*** +#### func NewHttpHandleWrapper(srv *Server, packer ContextPacker[Context]) *Http[Context] + +> 创建一个新的 http 处理程序包装器 +> - 默认使用 server.HttpContext 作为上下文,如果需要依赖其作为新的上下文,可以通过 NewHttpContext 创建 + +*** +#### func NewHttpContext(ctx *gin.Context) *HttpContext + +> 基于 gin.Context 创建一个新的 HttpContext + +*** +#### func NewGinWrapper(server *gin.Engine, pack func (ctx *gin.Context) CTX) *HttpWrapper[CTX] + +> 创建 gin 包装器,用于对 NewHttpWrapper 函数的替代 + +*** +#### func HasMessageType(mt MessageType) bool + +> 检查是否存在指定的消息类型 + +*** +#### func NewMultipleServer(serverHandle ...func () ((addr string, srv *Server))) *MultipleServer + + +*** +#### func GetNetworks() []Network + +> 获取所有支持的网络模式 + +*** +#### func WithLowMessageDuration(duration time.Duration) Option + +> 通过指定慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志 +> - 默认值为 DefaultLowMessageDuration +> - 当 duration <= 0 时,表示关闭慢消息检测 + +*** +#### func WithAsyncLowMessageDuration(duration time.Duration) Option + +> 通过指定异步消息的慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志 +> - 默认值为 DefaultAsyncLowMessageDuration +> - 当 duration <= 0 时,表示关闭慢消息检测 + +*** +#### func WithWebsocketConnInitializer(initializer func (writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error) Option + +> 通过 websocket 连接初始化的方式创建服务器,当 initializer 返回错误时,服务器将不会处理该连接的后续逻辑 +> - 该选项仅在创建 NetworkWebsocket 服务器时有效 + +*** +#### func WithWebsocketUpgrade(upgrader *websocket.Upgrader) Option + +> 通过指定 websocket.Upgrader 的方式创建服务器 +> - 默认值为 DefaultWebsocketUpgrader +> - 该选项仅在创建 NetworkWebsocket 服务器时有效 + +*** +#### func WithConnWriteBufferSize(size int) Option + +> 通过连接写入缓冲区大小的方式创建服务器 +> - 默认值为 DefaultConnWriteBufferSize +> - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存 + +*** +#### func WithDispatcherBufferSize(size int) Option + +> 通过消息分发器缓冲区大小的方式创建服务器 +> - 默认值为 DefaultDispatcherBufferSize +> - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存 + +*** +#### func WithMessageStatistics(duration time.Duration, limit int) Option + +> 通过消息统计的方式创建服务器 +> - 默认不开启,当 duration 和 limit 均大于 0 的时候,服务器将记录每 duration 期间的消息数量,并保留最多 limit 条 + +*** +#### func WithPacketWarnSize(size int) Option + +> 通过数据包大小警告的方式创建服务器,当数据包大小超过指定大小时,将会输出 WARN 类型的日志 +> - 默认值为 DefaultPacketWarnSize +> - 当 size <= 0 时,表示不设置警告 + +*** +#### func WithLimitLife(t time.Duration) Option + +> 通过限制最大生命周期的方式创建服务器 +> - 通常用于测试服务器,服务器将在到达最大生命周期时自动关闭 + +*** +#### func WithWebsocketWriteCompression() Option + +> 通过数据写入压缩的方式创建Websocket服务器 +> - 默认不开启数据压缩 + +*** +#### func WithWebsocketCompression(level int) Option + +> 通过数据压缩的方式创建Websocket服务器 +> - 默认不开启数据压缩 + +*** +#### func WithDeadlockDetect(t time.Duration) Option + +> 通过死锁、死循环、永久阻塞检测的方式创建服务器 +> - 当检测到死锁、死循环、永久阻塞时,服务器将会生成 WARN 类型的日志,关键字为 "SuspectedDeadlock" +> - 默认不开启死锁检测 + +*** +#### func WithDisableAsyncMessage() Option + +> 通过禁用异步消息的方式创建服务器 + +*** +#### func WithAsyncPoolSize(size int) Option + +> 通过指定异步消息池大小的方式创建服务器 +> - 当通过 WithDisableAsyncMessage 禁用异步消息时,此选项无效 +> - 默认值为 DefaultAsyncPoolSize + +*** +#### func WithWebsocketReadDeadline(t time.Duration) Option + +> 设置 Websocket 读取超时时间 +> - 默认: DefaultWebsocketReadDeadline +> - 当 t <= 0 时,表示不设置超时时间 + +*** +#### func WithTicker(poolSize int, size int, connSize int, autonomy bool) Option + +> 通过定时器创建服务器,为服务器添加定时器功能 +> - poolSize:指定服务器定时器池大小,当池子内的定时器数量超出该值后,多余的定时器在释放时将被回收,该值小于等于 0 时将使用 timer.DefaultTickerPoolSize +> - size:服务器定时器时间轮大小 +> - connSize:服务器连接定时器时间轮大小,当该值小于等于 0 的时候,在新连接建立时将不再为其创建定时器 +> - autonomy:定时器是否独立运行(独立运行的情况下不会作为服务器消息运行,会导致并发问题) + +*** +#### func WithTLS(certFile string, keyFile string) Option + +> 通过安全传输层协议TLS创建服务器 +> - 支持:Http、Websocket + +*** +#### func WithGRPCServerOptions(options ...grpc.ServerOption) Option + +> 通过GRPC的可选项创建GRPC服务器 + +*** +#### func WithWebsocketMessageType(messageTypes ...int) Option + +> 设置仅支持特定类型的Websocket消息 + +*** +#### func WithPProf(pattern ...string) Option + +> 通过性能分析工具PProf创建服务器 + +*** +#### func New(network Network, options ...Option) *Server + +> 根据特定网络类型创建一个服务器 + +示例代码: +```go + +func ExampleNew() { + srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { + conn.Write(packet) + }) + if err := srv.Run(":9999"); err != nil { + panic(err) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNew(t *testing.T) { + srv := server.New(server.NetworkWebsocket, server.WithPProf()) + srv.RegStartBeforeEvent(func(srv *server.Server) { + fmt.Println("启动前") + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + fmt.Println("启动完成") + }) + srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { + fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount()) + }) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { + conn.Write(packet) + }) + if err := srv.Run(":9999"); err != nil { + panic(err) + } +} + +``` + + +
+ + +*** +#### func BindService(srv *Server, services ...Service) + +> 绑定服务到特定 Server,被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法 + +示例代码: +```go + +func ExampleBindService() { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) + server.BindService(srv, new(TestService)) + if err := srv.RunNone(); err != nil { + panic(err) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestBindService(t *testing.T) { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) + server.BindService(srv, new(TestService)) + if err := srv.RunNone(); err != nil { + t.Fatal(err) + } +} + +``` + + +
+ + +*** +### Bot `STRUCT` + +```go +type Bot struct { + conn *Conn + joined atomic.Bool +} +``` +#### func (*Bot) JoinServer() +> 加入服务器 +*** +#### func (*Bot) LeaveServer() +> 离开服务器 +*** +#### func (*Bot) SetNetworkDelay(delay time.Duration, fluctuation time.Duration) +> 设置网络延迟和波动范围 +> - delay 延迟 +> - fluctuation 波动范围 +*** +#### func (*Bot) SetWriter(writer io.Writer) +> 设置写入器 +*** +#### func (*Bot) SendPacket(packet []byte) +> 发送数据包到服务器 +*** +#### func (*Bot) SendWSPacket(wst int, packet []byte) +> 发送 WebSocket 数据包到服务器 +*** +### BotOption `STRUCT` + +```go +type BotOption func(bot *Bot) +``` +### Conn `STRUCT` +服务器连接单次消息的包装 +```go +type Conn struct { + *connection + wst int + ctx context.Context +} +``` +#### func (*Conn) Ticker() *timer.Ticker +> 获取定时器 +*** +#### func (*Conn) GetServer() *Server +> 获取服务器 +*** +#### func (*Conn) GetOpenTime() time.Time +> 获取连接打开时间 +*** +#### func (*Conn) GetOnlineTime() time.Duration +> 获取连接在线时长 +*** +#### func (*Conn) GetWebsocketRequest() *http.Request +> 获取websocket请求 +*** +#### func (*Conn) IsBot() bool +> 是否是机器人连接 +*** +#### func (*Conn) RemoteAddr() net.Addr +> 获取远程地址 +*** +#### func (*Conn) GetID() string +> 获取连接ID +> - 为远程地址的字符串形式 +*** +#### func (*Conn) GetIP() string +> 获取连接IP +*** +#### func (*Conn) IsClosed() bool +> 是否已经关闭 +*** +#### func (*Conn) SetData(key any, value any) *Conn +> 设置连接数据,该数据将在连接关闭前始终存在 +*** +#### func (*Conn) GetData(key any) any +> 获取连接数据 +*** +#### func (*Conn) ViewData() map[any]any +> 查看只读的连接数据 +*** +#### func (*Conn) SetMessageData(key any, value any) *Conn +> 设置消息数据,该数据将在消息处理完成后释放 +*** +#### func (*Conn) GetMessageData(key any) any +> 获取消息数据 +*** +#### func (*Conn) ReleaseData() *Conn +> 释放数据 +*** +#### func (*Conn) IsWebsocket() bool +> 是否是websocket连接 +*** +#### func (*Conn) GetWST() int +> 获取本次 websocket 消息类型 +> - 默认将与发送类型相同 +*** +#### func (*Conn) SetWST(wst int) *Conn +> 设置本次 websocket 消息类型 +*** +#### func (*Conn) PushAsyncMessage(caller func () error, callback func (err error), mark ...log.Field) +> 推送异步消息,该消息将通过 Server.PushShuntAsyncMessage 函数推送 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +*** +#### func (*Conn) PushUniqueAsyncMessage(name string, caller func () error, callback func (err error), mark ...log.Field) +> 推送唯一异步消息,该消息将通过 Server.PushUniqueShuntAsyncMessage 函数推送 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +> - 不同的是当上一个相同的 unique 消息未执行完成时,将会忽略该消息 +*** +#### func (*Conn) Write(packet []byte, callback ...func (err error)) +> 向连接中写入数据 +*** +#### func (*Conn) Close(err ...error) +> 关闭连接 +*** +### ConsoleParams `STRUCT` +控制台参数 +```go +type ConsoleParams map[string][]string +``` +#### func (ConsoleParams) Get(key string) string +> 获取参数值 +*** +#### func (ConsoleParams) GetValues(key string) []string +> 获取参数值 +*** +#### func (ConsoleParams) GetValueNum(key string) int +> 获取参数值数量 +*** +#### func (ConsoleParams) Has(key string) bool +> 是否存在参数 +*** +#### func (ConsoleParams) Add(key string, value string) +> 添加参数 +*** +#### func (ConsoleParams) Del(key string) +> 删除参数 +*** +#### func (ConsoleParams) Clear() +> 清空参数 +*** +### MessageReadyEventHandler `STRUCT` + +```go +type MessageReadyEventHandler func(srv *Server) +``` +### Http `STRUCT` +基于 gin.Engine 包装的 http 服务器 +```go +type Http[Context any] struct { + srv *Server + *gin.Engine + *HttpRouter[Context] +} +``` +#### func (*Http) Gin() *gin.Engine +*** +### HttpContext `STRUCT` +基于 gin.Context 的 http 请求上下文 +```go +type HttpContext struct { + *gin.Context +} +``` +#### func (*HttpContext) Gin() *gin.Context +> 获取 gin.Context +*** +#### func (*HttpContext) ReadTo(dest any) error +> 读取请求数据到指定结构体,如果失败则返回错误 +*** +### HandlerFunc `STRUCT` + +```go +type HandlerFunc[Context any] func(ctx Context) +``` +### ContextPacker `STRUCT` + +```go +type ContextPacker[Context any] func(ctx *gin.Context) Context +``` +### HttpRouter `STRUCT` + +```go +type HttpRouter[Context any] struct { + srv *Server + group gin.IRouter + packer ContextPacker[Context] +} +``` +#### func (*HttpRouter) Handle(httpMethod string, relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 使用给定的路径和方法注册新的请求句柄和中间件 +> - 最后一个处理程序应该是真正的处理程序,其他处理程序应该是可以而且应该在不同路由之间共享的中间件。 +*** +#### func (*HttpRouter) POST(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("POST", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) GET(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("GET", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) DELETE(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("DELETE", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) PATCH(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("PATCH", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) PUT(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("PUT", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) OPTIONS(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("OPTIONS", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) HEAD(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("HEAD", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) CONNECT(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("CONNECT", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) TRACE(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 是 Handle("TRACE", path, handlers) 的快捷方式 +*** +#### func (*HttpRouter) Any(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 注册一个匹配所有 HTTP 方法的路由 +> - GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. +*** +#### func (*HttpRouter) Match(methods []string, relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 注册一个匹配指定 HTTP 方法的路由 +> - GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. +*** +#### func (*HttpRouter) StaticFile(relativePath string, filepath string) *HttpRouter[Context] +> 注册单个路由以便为本地文件系统的单个文件提供服务。 +> - 例如: StaticFile("favicon.ico", "./resources/favicon.ico") +*** +#### func (*HttpRouter) StaticFileFS(relativePath string, filepath string, fs http.FileSystem) *HttpRouter[Context] +> 与 `StaticFile` 类似,但可以使用自定义的 `http.FileSystem` 代替。 +> - 例如: StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) +> - 由于依赖于 gin.Engine 默认情况下使用:gin.Dir +*** +#### func (*HttpRouter) Static(relativePath string, root string) *HttpRouter[Context] +> 提供来自给定文件系统根目录的文件。 +> - 例如: Static("/static", "/var/www") +*** +#### func (*HttpRouter) StaticFS(relativePath string, fs http.FileSystem) *HttpRouter[Context] +> 与 `Static` 类似,但可以使用自定义的 `http.FileSystem` 代替。 +> - 例如: StaticFS("/static", Dir{"/var/www", false}) +> - 由于依赖于 gin.Engine 默认情况下使用:gin.Dir +*** +#### func (*HttpRouter) Group(relativePath string, handlers ...HandlerFunc[Context]) *HttpRouter[Context] +> 创建一个新的路由组。您应该添加所有具有共同中间件的路由。 +> - 例如: v1 := slf.Group("/v1") +*** +#### func (*HttpRouter) Use(middleware ...HandlerFunc[Context]) *HttpRouter[Context] +> 将中间件附加到路由组。 +*** +### HttpWrapperHandleFunc `STRUCT` + +```go +type HttpWrapperHandleFunc[CTX any] func(ctx CTX) +``` +### HttpWrapper `STRUCT` +http 包装器 +```go +type HttpWrapper[CTX any] struct { + server *gin.Engine + packHandle func(ctx *gin.Context) CTX +} +``` +#### func (*HttpWrapper) Handle(httpMethod string, relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 处理请求 +*** +#### func (*HttpWrapper) Use(middleware ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 使用中间件 +*** +#### func (*HttpWrapper) GET(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 GET 请求 +*** +#### func (*HttpWrapper) POST(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 POST 请求 +*** +#### func (*HttpWrapper) DELETE(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 DELETE 请求 +*** +#### func (*HttpWrapper) PATCH(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 PATCH 请求 +*** +#### func (*HttpWrapper) PUT(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 PUT 请求 +*** +#### func (*HttpWrapper) OPTIONS(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 OPTIONS 请求 +*** +#### func (*HttpWrapper) HEAD(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 HEAD 请求 +*** +#### func (*HttpWrapper) Trace(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 Trace 请求 +*** +#### func (*HttpWrapper) Connect(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 Connect 请求 +*** +#### func (*HttpWrapper) Any(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册 Any 请求 +*** +#### func (*HttpWrapper) Match(methods []string, relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapper[CTX] +> 注册与您声明的指定方法相匹配的路由。 +*** +#### func (*HttpWrapper) StaticFile(relativePath string, filepath string) *HttpWrapper[CTX] +> 注册 StaticFile 请求 +*** +#### func (*HttpWrapper) Static(relativePath string, root string) *HttpWrapper[CTX] +> 注册 Static 请求 +*** +#### func (*HttpWrapper) StaticFS(relativePath string, fs http.FileSystem) *HttpWrapper[CTX] +> 注册 StaticFS 请求 +*** +#### func (*HttpWrapper) Group(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 创建一个新的路由组。您应该添加所有具有共同中间件的路由。 +*** +### HttpWrapperGroup `STRUCT` +http 包装器 +```go +type HttpWrapperGroup[CTX any] struct { + wrapper *HttpWrapper[CTX] + group *gin.RouterGroup +} +``` +#### func (*HttpWrapperGroup) Handle(httpMethod string, relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 处理请求 +*** +#### func (*HttpWrapperGroup) Use(middleware ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 使用中间件 +*** +#### func (*HttpWrapperGroup) GET(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 注册 GET 请求 +*** +#### func (*HttpWrapperGroup) POST(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 注册 POST 请求 +*** +#### func (*HttpWrapperGroup) DELETE(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 注册 DELETE 请求 +*** +#### func (*HttpWrapperGroup) PATCH(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 注册 PATCH 请求 +*** +#### func (*HttpWrapperGroup) PUT(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 注册 PUT 请求 +*** +#### func (*HttpWrapperGroup) OPTIONS(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 注册 OPTIONS 请求 +*** +#### func (*HttpWrapperGroup) Group(relativePath string, handlers ...HttpWrapperHandleFunc[CTX]) *HttpWrapperGroup[CTX] +> 创建分组 +*** +### MessageType `STRUCT` + +```go +type MessageType byte +``` +#### func (MessageType) String() string +> 返回消息类型的字符串表示 +*** +### Message `STRUCT` +服务器消息 +```go +type Message struct { + dis *dispatcher.Dispatcher[string, *Message] + conn *Conn + err error + ordinaryHandler func() + exceptionHandler func() error + errHandler func(err error) + marks []log.Field + packet []byte + producer string + name string + t MessageType +} +``` +#### func (*Message) GetProducer() string +*** +#### func (*Message) MessageType() MessageType +> 返回消息类型 +*** +#### func (*Message) String() string +> 返回消息的字符串表示 +*** +### MultipleServer `STRUCT` + +```go +type MultipleServer struct { + servers []*Server + addresses []string + exitEventHandles []func() +} +``` +#### func (*MultipleServer) Run() +*** +#### func (*MultipleServer) RegExitEvent(handle func ()) +> 注册退出事件 +*** +#### func (*MultipleServer) OnExitEvent() +*** +### Network `STRUCT` + +```go +type Network string +``` +#### func (Network) IsSocket() bool +> 返回当前服务器的网络模式是否为 Socket 模式 +*** +### Option `STRUCT` + +```go +type Option func(srv *Server) +``` +### Server `STRUCT` +网络服务器 +```go +type Server struct { + *event + *runtime + *option + *connMgr + dispatcherMgr *dispatcher.Manager[string, *Message] + ginServer *gin.Engine + httpServer *http.Server + grpcServer *grpc.Server + gServer *gNet + multiple *MultipleServer + ants *ants.Pool + messagePool *hub.ObjectPool[*Message] + ctx context.Context + cancel context.CancelFunc + systemSignal chan os.Signal + closeChannel chan struct{} + multipleRuntimeErrorChan chan error + messageCounter atomic.Int64 + addr string + network Network + closed uint32 + services []func() +} +``` +#### func (*Server) Run(addr string) (err error) +> 使用特定地址运行服务器 +> - server.NetworkTcp (addr:":8888") +> - server.NetworkTcp4 (addr:":8888") +> - server.NetworkTcp6 (addr:":8888") +> - server.NetworkUdp (addr:":8888") +> - server.NetworkUdp4 (addr:":8888") +> - server.NetworkUdp6 (addr:":8888") +> - server.NetworkUnix (addr:"socketPath") +> - server.NetworkHttp (addr:":8888") +> - server.NetworkWebsocket (addr:":8888/ws") +> - server.NetworkKcp (addr:":8888") +> - server.NetworkNone (addr:"") +示例代码: +```go + +func ExampleServer_Run() { + srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { + conn.Write(packet) + }) + if err := srv.Run(":9999"); err != nil { + panic(err) + } +} + +``` + +*** +#### func (*Server) IsSocket() bool +> 是否是 Socket 模式 +*** +#### func (*Server) RunNone() error +> 是 Run("") 的简写,仅适用于运行 NetworkNone 服务器 +*** +#### func (*Server) Context() context.Context +> 获取服务器上下文 +*** +#### func (*Server) TimeoutContext(timeout time.Duration) context.Context, context.CancelFunc +> 获取服务器超时上下文,context.WithTimeout 的简写 +*** +#### func (*Server) Ticker() *timer.Ticker +> 获取服务器定时器 +*** +#### func (*Server) Shutdown() +> 主动停止运行服务器 +*** +#### func (*Server) GRPCServer() *grpc.Server +> 当网络类型为 NetworkGRPC 时将被允许获取 grpc 服务器,否则将会发生 panic +*** +#### func (*Server) HttpRouter() gin.IRouter +> 当网络类型为 NetworkHttp 时将被允许获取路由器进行路由注册,否则将会发生 panic +> - 通过该函数注册的路由将无法在服务器关闭时正常等待请求结束 +> +> Deprecated: 从 Minotaur 0.0.29 开始,由于设计原因已弃用,该函数将直接返回 *gin.Server 对象,导致无法正常的对请求结束时进行处理 +*** +#### func (*Server) HttpServer() *Http[*HttpContext] +> 替代 HttpRouter 的函数,返回一个 *Http[*HttpContext] 对象 +> - 通过该函数注册的路由将在服务器关闭时正常等待请求结束 +> - 如果需要自行包装 Context 对象,可以使用 NewHttpHandleWrapper 方法 +*** +#### func (*Server) GetMessageCount() int64 +> 获取当前服务器中消息的数量 +*** +#### func (*Server) UseShunt(conn *Conn, name string) +> 切换连接所使用的消息分流渠道,当分流渠道 name 不存在时将会创建一个新的分流渠道,否则将会加入已存在的分流渠道 +> - 默认情况下,所有连接都使用系统通道进行消息分发,当指定消息分流渠道且为分流消息类型时,将会使用指定的消息分流渠道进行消息分发 +> - 分流渠道会在连接断开时标记为驱逐状态,当分流渠道中的所有消息处理完毕且没有新连接使用时,将会被清除 +*** +#### func (*Server) HasShunt(name string) bool +> 检查特定消息分流渠道是否存在 +*** +#### func (*Server) GetConnCurrShunt(conn *Conn) string +> 获取连接当前所使用的消息分流渠道 +*** +#### func (*Server) GetShuntNum() int +> 获取消息分流渠道数量 +*** +#### func (*Server) PushSystemMessage(handler func (), mark ...log.Field) +> 向服务器中推送 MessageTypeSystem 消息 +> - 系统消息仅包含一个可执行函数,将在系统分发器中执行 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +*** +#### func (*Server) PushAsyncMessage(caller func () error, callback func (err error), mark ...log.Field) +> 向服务器中推送 MessageTypeAsync 消息 +> - 异步消息将在服务器的异步消息队列中进行处理,处理完成 caller 的阻塞操作后,将会通过系统消息执行 callback 函数 +> - callback 函数将在异步消息处理完成后进行调用,无论过程是否产生 err,都将被执行,允许为 nil +> - 需要注意的是,为了避免并发问题,caller 函数请仅处理阻塞操作,其他操作应该在 callback 函数中进行 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +*** +#### func (*Server) PushShuntAsyncMessage(conn *Conn, caller func () error, callback func (err error), mark ...log.Field) +> 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 +> - 需要注意的是,当未指定 UseShunt 时,将会通过 PushAsyncMessage 进行转发 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +*** +#### func (*Server) PushPacketMessage(conn *Conn, wst int, packet []byte, mark ...log.Field) +> 向服务器中推送 MessageTypePacket 消息 +> - 当存在 UseShunt 的选项时,将会根据选项中的 shuntMatcher 进行分发,否则将在系统分发器中处理消息 +*** +#### func (*Server) PushTickerMessage(name string, caller func (), mark ...log.Field) +> 向服务器中推送 MessageTypeTicker 消息 +> - 通过该函数推送定时消息,当消息触发时将在系统分发器中处理消息 +> - 可通过 timer.Ticker 或第三方定时器将执行函数(caller)推送到该消息中进行处理,可有效的避免线程安全问题 +> - 参数 name 仅用作标识该定时器名称 +> +> 定时消息执行不会有特殊的处理,仅标记为定时任务,也就是允许将各类函数通过该消息发送处理,但是并不建议 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +*** +#### func (*Server) PushShuntTickerMessage(conn *Conn, name string, caller func (), mark ...log.Field) +> 向特定分发器中推送 MessageTypeTicker 消息,消息执行与 MessageTypeTicker 一致 +> - 需要注意的是,当未指定 UseShunt 时,将会通过 PushTickerMessage 进行转发 +> - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 +*** +#### func (*Server) PushUniqueAsyncMessage(unique string, caller func () error, callback func (err error), mark ...log.Field) +> 向服务器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 +> - 不同的是当上一个相同的 unique 消息未执行完成时,将会忽略该消息 +*** +#### func (*Server) PushUniqueShuntAsyncMessage(conn *Conn, unique string, caller func () error, callback func (err error), mark ...log.Field) +> 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 +> - 需要注意的是,当未指定 UseShunt 时,将会通过系统分流渠道进行转发 +> - 不同的是当上一个相同的 unique 消息未执行完成时,将会忽略该消息 +*** +#### func (*Server) PushShuntMessage(conn *Conn, caller func (), mark ...log.Field) +> 向特定分发器中推送 MessageTypeShunt 消息,消息执行与 MessageTypeSystem 一致,不同的是将会在特定分发器中执行 +*** +#### func (*Server) GetDurationMessageCount() int64 +> 获取当前 WithMessageStatistics 设置的 duration 期间的消息量 +*** +#### func (*Server) GetDurationMessageCountByOffset(offset int) int64 +> 获取特定偏移次数的 WithMessageStatistics 设置的 duration 期间的消息量 +> - 该值小于 0 时,将与 GetDurationMessageCount 无异,否则将返回 +n 个期间的消息量,例如 duration 为 1 分钟,limit 为 10,那么 offset 为 1 的情况下,获取的则是上一分钟消息量 +*** +#### func (*Server) GetAllDurationMessageCount() []int64 +> 获取所有 WithMessageStatistics 设置的 duration 期间的消息量 +*** +#### func (*Server) HasMessageStatistics() bool +> 是否了开启消息统计 +*** +### Service `INTERFACE` +兼容传统 service 设计模式的接口 +```go +type Service interface { + OnInit(srv *Server) +} +``` diff --git a/server/client/README.md b/server/client/README.md new file mode 100644 index 0000000..ad20045 --- /dev/null +++ b/server/client/README.md @@ -0,0 +1,265 @@ +# Client + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewClient](#NewClient)|创建客户端 +|[CloneClient](#CloneClient)|克隆客户端 +|[NewTCP](#NewTCP)|暂无描述... +|[NewUnixDomainSocket](#NewUnixDomainSocket)|暂无描述... +|[NewWebsocket](#NewWebsocket)|创建 websocket 客户端 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Client](#client)|客户端 +|`INTERFACE`|[Core](#core)|暂无描述... +|`STRUCT`|[ConnectionClosedEventHandle](#connectionclosedeventhandle)|暂无描述... +|`STRUCT`|[Packet](#packet)|暂无描述... +|`STRUCT`|[TCP](#tcp)|暂无描述... +|`STRUCT`|[UnixDomainSocket](#unixdomainsocket)|暂无描述... +|`STRUCT`|[Websocket](#websocket)|websocket 客户端 + +
+ + +*** +## 详情信息 +#### func NewClient(core Core) *Client + +> 创建客户端 + +*** +#### func CloneClient(client *Client) *Client + +> 克隆客户端 + +*** +#### func NewTCP(addr string) *Client + + +*** +#### func NewUnixDomainSocket(addr string) *Client + + +*** +#### func NewWebsocket(addr string) *Client + +> 创建 websocket 客户端 + +*** +### Client `STRUCT` +客户端 +```go +type Client struct { + *events + core Core + mutex sync.Mutex + closed bool + pool *hub.ObjectPool[*Packet] + loop *writeloop.Channel[*Packet] + loopBufferSize int + block chan struct{} +} +``` +#### func (*Client) Run(block ...bool) error +> 运行客户端,当客户端已运行时,会先关闭客户端再重新运行 +> - block 以阻塞方式运行 +*** +#### func (*Client) RunByBufferSize(size int, block ...bool) error +> 指定写入循环缓冲区大小运行客户端,当客户端已运行时,会先关闭客户端再重新运行 +> - block 以阻塞方式运行 +*** +#### func (*Client) IsConnected() bool +> 是否已连接 +*** +#### func (*Client) Close(err ...error) +> 关闭 +*** +#### func (*Client) WriteWS(wst int, packet []byte, callback ...func (err error)) +> 向连接中写入指定 websocket 数据类型 +> - wst: websocket模式中指定消息类型 +
+查看 / 收起单元测试 + + +```go + +func TestClient_WriteWS(t *testing.T) { + var wait sync.WaitGroup + wait.Add(1) + srv := server.New(server.NetworkWebsocket) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { + srv.Shutdown() + }) + srv.RegStopEvent(func(srv *server.Server) { + wait.Done() + }) + srv.RegMessageReadyEvent(func(srv *server.Server) { + cli := client.NewWebsocket("ws://127.0.0.1:9999") + cli.RegConnectionOpenedEvent(func(conn *client.Client) { + conn.WriteWS(2, []byte("Hello")) + }) + if err := cli.Run(); err != nil { + panic(err) + } + }) + if err := srv.Run(":9999"); err != nil { + panic(err) + } + wait.Wait() +} + +``` + + +
+ + +*** +#### func (*Client) Write(packet []byte, callback ...func (err error)) +> 向连接中写入数据 +*** +#### func (*Client) GetServerAddr() string +> 获取服务器地址 +*** +### Core `INTERFACE` + +```go +type Core interface { + Run(runState chan error, receive func(wst int, packet []byte)) + Write(packet *Packet) error + Close() + GetServerAddr() string + Clone() Core +} +``` +### ConnectionClosedEventHandle `STRUCT` + +```go +type ConnectionClosedEventHandle func(conn *Client, err any) +``` +### Packet `STRUCT` + +```go +type Packet struct { + wst int + data []byte + callback func(err error) +} +``` +### TCP `STRUCT` + +```go +type TCP struct { + conn net.Conn + addr string + closed bool +} +``` +#### func (*TCP) Run(runState chan error, receive func (wst int, packet []byte)) +*** +#### func (*TCP) Write(packet *Packet) error +*** +#### func (*TCP) Close() +*** +#### func (*TCP) GetServerAddr() string +*** +#### func (*TCP) Clone() Core +*** +### UnixDomainSocket `STRUCT` + +```go +type UnixDomainSocket struct { + conn net.Conn + addr string + closed bool +} +``` +#### func (*UnixDomainSocket) Run(runState chan error, receive func (wst int, packet []byte)) +*** +#### func (*UnixDomainSocket) Write(packet *Packet) error +
+查看 / 收起单元测试 + + +```go + +func TestUnixDomainSocket_Write(t *testing.T) { + var closed = make(chan struct{}) + srv := server.New(server.NetworkUnix) + srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { + t.Log(string(packet)) + conn.Write(packet) + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + time.Sleep(time.Second) + cli := client.NewUnixDomainSocket("./test.sock") + cli.RegConnectionOpenedEvent(func(conn *client.Client) { + conn.Write([]byte("Hello~")) + }) + cli.RegConnectionReceivePacketEvent(func(conn *client.Client, wst int, packet []byte) { + t.Log(packet) + closed <- struct{}{} + }) + if err := cli.Run(); err != nil { + panic(err) + } + }) + go func() { + if err := srv.Run("./test.sock"); err != nil { + panic(err) + } + }() + <-closed + srv.Shutdown() +} + +``` + + +
+ + +*** +#### func (*UnixDomainSocket) Close() +*** +#### func (*UnixDomainSocket) GetServerAddr() string +*** +#### func (*UnixDomainSocket) Clone() Core +*** +### Websocket `STRUCT` +websocket 客户端 +```go +type Websocket struct { + addr string + conn *websocket.Conn + closed bool + mu sync.Mutex +} +``` +#### func (*Websocket) Run(runState chan error, receive func (wst int, packet []byte)) +*** +#### func (*Websocket) Write(packet *Packet) error +*** +#### func (*Websocket) Close() +*** +#### func (*Websocket) GetServerAddr() string +*** +#### func (*Websocket) Clone() Core +*** diff --git a/server/gateway/README.md b/server/gateway/README.md new file mode 100644 index 0000000..831a22f --- /dev/null +++ b/server/gateway/README.md @@ -0,0 +1,249 @@ +# Gateway + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +gateway 是用于处理服务器消息的网关模块,适用于对客户端消息进行处理、转发的情况。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewEndpoint](#NewEndpoint)|创建网关端点 +|[WithEndpointStateEvaluator](#WithEndpointStateEvaluator)|设置端点健康值评估函数 +|[WithEndpointConnectionPoolSize](#WithEndpointConnectionPoolSize)|设置端点连接池大小 +|[WithEndpointReconnectInterval](#WithEndpointReconnectInterval)|设置端点重连间隔 +|[NewGateway](#NewGateway)|基于 server.Server 创建 Gateway 网关服务器 +|[WithEndpointSelector](#WithEndpointSelector)|设置端点选择器 +|[MarshalGatewayOutPacket](#MarshalGatewayOutPacket)|将数据包转换为网关出网数据包 +|[UnmarshalGatewayOutPacket](#UnmarshalGatewayOutPacket)|将网关出网数据包转换为数据包 +|[MarshalGatewayInPacket](#MarshalGatewayInPacket)|将数据包转换为网关入网数据包 +|[UnmarshalGatewayInPacket](#UnmarshalGatewayInPacket)|将网关入网数据包转换为数据包 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Endpoint](#endpoint)|网关端点 +|`STRUCT`|[EndpointOption](#endpointoption)|网关端点选项 +|`STRUCT`|[ConnectionOpenedEventHandle](#connectionopenedeventhandle)|暂无描述... +|`STRUCT`|[EndpointSelector](#endpointselector)|暂无描述... +|`STRUCT`|[Gateway](#gateway)|基于 server.Server 实现的网关服务器 +|`STRUCT`|[Option](#option)|网关选项 +|`INTERFACE`|[Scanner](#scanner)|端点扫描器 + +
+ + +*** +## 详情信息 +#### func NewEndpoint(name string, cli *client.Client, options ...EndpointOption) *Endpoint + +> 创建网关端点 + +*** +#### func WithEndpointStateEvaluator(evaluator func (costUnixNano float64) float64) EndpointOption + +> 设置端点健康值评估函数 + +*** +#### func WithEndpointConnectionPoolSize(size int) EndpointOption + +> 设置端点连接池大小 +> - 默认为 DefaultEndpointConnectionPoolSize +> - 端点连接池大小决定了网关服务器与端点服务器建立的连接数,如果 <= 0 则会使用默认值 +> - 在网关服务器中,多个客户端在发送消息到端点服务器时,会共用一个连接,适当的增大连接池大小可以提高网关服务器的承载能力 + +*** +#### func WithEndpointReconnectInterval(interval time.Duration) EndpointOption + +> 设置端点重连间隔 +> - 默认为 DefaultEndpointReconnectInterval +> - 端点在连接失败后会在该间隔后重连,如果 <= 0 则不会重连 + +*** +#### func NewGateway(srv *server.Server, scanner Scanner, options ...Option) *Gateway + +> 基于 server.Server 创建 Gateway 网关服务器 + +*** +#### func WithEndpointSelector(selector EndpointSelector) Option + +> 设置端点选择器 +> - 默认情况下,网关会随机选择一个端点作为目标,如果需要自定义端点选择器,可以通过该选项设置 + +*** +#### func MarshalGatewayOutPacket(addr string, packet []byte) []byte, error + +> 将数据包转换为网关出网数据包 +> - | identifier(4) | ipv4(4) | port(2) | packet | + +*** +#### func UnmarshalGatewayOutPacket(data []byte) (addr string, packet []byte, err error) + +> 将网关出网数据包转换为数据包 +> - | identifier(4) | ipv4(4) | port(2) | packet | + +*** +#### func MarshalGatewayInPacket(addr string, currentTime int64, packet []byte) []byte, error + +> 将数据包转换为网关入网数据包 +> - | ipv4(4) | port(2) | cost(4) | packet | + +*** +#### func UnmarshalGatewayInPacket(data []byte) (addr string, sendTime int64, packet []byte, err error) + +> 将网关入网数据包转换为数据包 +> - | ipv4(4) | port(2) | cost(4) | packet | + +*** +### Endpoint `STRUCT` +网关端点 + - 每一个端点均表示了一个目标服务,网关会将数据包转发到该端点,由该端点负责将数据包转发到目标服务。 + - 每个端点会建立一个连接池,默认大小为 DefaultEndpointConnectionPoolSize,可通过 WithEndpointConnectionPoolSize 进行设置。 + - 网关在转发数据包时会自行根据延迟维护端点健康值,端点健康值越高,网关越倾向于将数据包转发到该端点。 + - 端点支持连接未中断前始终将数据包转发到特定端点,这样可以保证连接的状态维持。 + +连接池: + - 连接池大小决定了网关服务器与端点服务器建立的连接数,例如当连接池大小为 1 时,那么所有连接到该端点的客户端都会共用一个连接。 + - 连接池的设计可以突破单机理论 65535 个 WebSocket 客户端的限制,适当的增大连接池大小可以提高网关服务器的承载能力。 +```go +type Endpoint struct { + gateway *Gateway + client []*client.Client + name string + address string + state atomic.Float64 + evaluator func(costUnixNano float64) float64 + connections *haxmap.Map[string, *server.Conn] + rci time.Duration + cps int +} +``` +#### func (*Endpoint) GetName() string +> 获取端点名称 +*** +#### func (*Endpoint) GetAddress() string +> 获取端点地址 +*** +#### func (*Endpoint) GetState() float64 +> 获取端点健康值 +*** +#### func (*Endpoint) Forward(conn *server.Conn, packet []byte, callback ...func (err error)) +> 转发数据包到该端点 +> - 端点在处理数据包时,应区分数据包为普通直连数据包还是网关数据包。可通过 UnmarshalGatewayOutPacket 进行数据包解析,当解析失败且无其他数据包协议时,可认为该数据包为普通直连数据包。 +*** +### EndpointOption `STRUCT` +网关端点选项 +```go +type EndpointOption func(endpoint *Endpoint) +``` +### ConnectionOpenedEventHandle `STRUCT` + +```go +type ConnectionOpenedEventHandle func(gateway *Gateway, conn *server.Conn) +``` +### EndpointSelector `STRUCT` + +```go +type EndpointSelector func(endpoints []*Endpoint) *Endpoint +``` +### Gateway `STRUCT` +基于 server.Server 实现的网关服务器 + - 网关服务器是一个特殊的服务器,它会通过扫描器扫描端点列表,然后连接到端点列表中的所有端点,当端点连接成功后,网关服务器会将客户端的连接数据转发到端点服务器 + - 由于该网关为多个客户端共享一个端点的连接,所以不会受限于单机 65535 个端口的限制 + - 需要注意的是,网关可以通过扫描器得到每个端点需要建立多少个连接,该连接总数将受限与 65535 个端口的限制 + +一些特性: + - 支持 server.Server 所支持的所有 Socket 网络类型 + - 支持将客户端网络类型进行不同的转换,例如:客户端使用 Websocket 连接,但是网关服务器可以将其转换为 TCP 端点的连接 + - 支持客户端消息绑定,在客户端未断开连接的情况下,可以将客户端的连接绑定到某个端点,这样该客户端的所有消息都会转发到该端点 + - 根据端点延迟实时调整端点状态评分,根据评分选择最优的端点,默认评分算法为:1 / (1 + 1.5 * ${DelaySeconds}) +```go +type Gateway struct { + *events + srv *server.Server + scanner Scanner + es map[string]map[string]*Endpoint + esm sync.Mutex + ess EndpointSelector + closed bool + running bool + cce map[string]*Endpoint + cceLock sync.RWMutex +} +``` +#### func (*Gateway) Run(addr string) error +> 运行网关 +
+查看 / 收起单元测试 + + +```go + +func TestGateway_Run(t *testing.T) { + gw := gateway.NewGateway(server.New(server.NetworkWebsocket, server.WithDeadlockDetect(time.Second*3)), new(Scanner)) + gw.RegConnectionReceivePacketEventHandle(func(gateway *gateway.Gateway, conn *server.Conn, packet []byte) { + endpoint, err := gateway.GetConnEndpoint("test", conn) + if err == nil { + endpoint.Forward(conn, packet) + } + }) + gw.RegEndpointConnectReceivePacketEventHandle(func(gateway *gateway.Gateway, endpoint *gateway.Endpoint, conn *server.Conn, packet []byte) { + conn.Write(packet) + }) + if err := gw.Run(":8888"); err != nil { + panic(err) + } +} + +``` + + +
+ + +*** +#### func (*Gateway) Shutdown() +> 关闭网关 +*** +#### func (*Gateway) Server() *server.Server +> 获取网关服务器核心 +*** +#### func (*Gateway) GetEndpoint(name string) *Endpoint, error +> 获取一个可用的端点 +> - name: 端点名称 +*** +#### func (*Gateway) GetConnEndpoint(name string, conn *server.Conn) *Endpoint, error +> 获取一个可用的端点,如果客户端已经连接到了某个端点,将优先返回该端点 +> - 当连接到的端点不可用或没有连接记录时,效果同 GetEndpoint 相同 +> - 当连接行为为有状态时,推荐使用该方法 +*** +#### func (*Gateway) SwitchEndpoint(source *Endpoint, dest *Endpoint) +> 将端点端点的所有连接切换到另一个端点 +*** +### Option `STRUCT` +网关选项 +```go +type Option func(gateway *Gateway) +``` +### Scanner `INTERFACE` +端点扫描器 +```go +type Scanner interface { + GetEndpoints() ([]*Endpoint, error) + GetInterval() time.Duration +} +``` +#### func (*Scanner) GetEndpoints() []*gateway.Endpoint, error +*** +#### func (*Scanner) GetInterval() time.Duration +*** diff --git a/server/internal/dispatcher/README.md b/server/internal/dispatcher/README.md new file mode 100644 index 0000000..ac41cf8 --- /dev/null +++ b/server/internal/dispatcher/README.md @@ -0,0 +1,239 @@ +# Dispatcher + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewDispatcher](#NewDispatcher)|创建一个新的消息分发器 Dispatcher 实例 +|[NewManager](#NewManager)|生成消息分发器管理器 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Action](#action)|消息分发器操作器,用于暴露外部可操作的消息分发器函数 +|`STRUCT`|[Handler](#handler)|消息处理器 +|`STRUCT`|[Dispatcher](#dispatcher)|用于服务器消息处理的消息分发器 +|`STRUCT`|[Manager](#manager)|消息分发器管理器 +|`INTERFACE`|[Message](#message)|暂无描述... +|`INTERFACE`|[Producer](#producer)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewDispatcher(bufferSize int, name string, handler Handler[P, M]) *Dispatcher[P, M] + +> 创建一个新的消息分发器 Dispatcher 实例 + +示例代码: +```go + +func ExampleNewDispatcher() { + m := new(atomic.Int64) + fm := new(atomic.Int64) + w := new(sync.WaitGroup) + w.Add(1) + d := dispatcher.NewDispatcher(1024, "example-dispatcher", func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + m.Add(1) + }) + d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) { + w.Done() + }) + var producers = []string{"producer1", "producer2", "producer3"} + for i := 0; i < len(producers); i++ { + p := producers[i] + for i := 0; i < 10; i++ { + d.Put(&TestMessage{producer: p}) + } + d.SetProducerDoneHandler(p, func(p string, dispatcher *dispatcher.Action[string, *TestMessage]) { + fm.Add(1) + }) + } + d.Start() + d.Expel() + w.Wait() + fmt.Println(fmt.Sprintf("producer num: %d, producer done: %d, finished: %d", len(producers), fm.Load(), m.Load())) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewDispatcher(t *testing.T) { + var cases = []struct { + name string + bufferSize int + handler dispatcher.Handler[string, *TestMessage] + shouldPanic bool + }{{name: "TestNewDispatcher_BufferSize0AndHandlerNil", bufferSize: 0, handler: nil, shouldPanic: true}, {name: "TestNewDispatcher_BufferSize0AndHandlerNotNil", bufferSize: 0, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }, shouldPanic: true}, {name: "TestNewDispatcher_BufferSize1AndHandlerNil", bufferSize: 1, handler: nil, shouldPanic: true}, {name: "TestNewDispatcher_BufferSize1AndHandlerNotNil", bufferSize: 1, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }, shouldPanic: false}} + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil && !c.shouldPanic { + t.Errorf("NewDispatcher() should not panic, but panic: %v", r) + } + }() + dispatcher.NewDispatcher(c.bufferSize, c.name, c.handler) + }) + } +} + +``` + + +
+ + +*** +#### func NewManager(bufferSize int, handler Handler[P, M]) *Manager[P, M] + +> 生成消息分发器管理器 + +示例代码: +```go + +func ExampleNewManager() { + mgr := dispatcher.NewManager[string, *TestMessage](10124*16, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }) + mgr.BindProducer("player_001", "shunt-001") + mgr.BindProducer("player_002", "shunt-002") + mgr.BindProducer("player_003", "shunt-sys") + mgr.BindProducer("player_004", "shunt-sys") + mgr.UnBindProducer("player_001") + mgr.UnBindProducer("player_002") + mgr.UnBindProducer("player_003") + mgr.UnBindProducer("player_004") + mgr.Wait() + fmt.Println("done") +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewManager(t *testing.T) { + var cases = []struct { + name string + bufferSize int + handler dispatcher.Handler[string, *TestMessage] + shouldPanic bool + }{{name: "TestNewManager_BufferSize0AndHandlerNil", bufferSize: 0, handler: nil, shouldPanic: true}, {name: "TestNewManager_BufferSize0AndHandlerNotNil", bufferSize: 0, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }, shouldPanic: true}, {name: "TestNewManager_BufferSize1AndHandlerNil", bufferSize: 1, handler: nil, shouldPanic: true}, {name: "TestNewManager_BufferSize1AndHandlerNotNil", bufferSize: 1, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) { + }, shouldPanic: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil && !c.shouldPanic { + t.Errorf("NewManager() should not panic, but panic: %v", r) + } + }() + dispatcher.NewManager[string, *TestMessage](c.bufferSize, c.handler) + }) + } +} + +``` + + +
+ + +*** +### Action `STRUCT` +消息分发器操作器,用于暴露外部可操作的消息分发器函数 +```go +type Action[P Producer, M Message[P]] struct { + unlock bool + d *Dispatcher[P, M] +} +``` +### Handler `STRUCT` +消息处理器 +```go +type Handler[P Producer, M Message[P]] func(dispatcher *Dispatcher[P, M], message M) +``` +### Dispatcher `STRUCT` +用于服务器消息处理的消息分发器 + +这个消息分发器为并发安全的生产者和消费者模型,生产者可以是任意类型,消费者必须是 Message 接口的实现。 +生产者可以通过 Put 方法并发安全地将消息放入消息分发器,消息执行过程不会阻塞到 Put 方法,同时允许在 Start 方法之前调用 Put 方法。 +在执行 Start 方法后,消息分发器会阻塞地从消息缓冲区中读取消息,然后执行消息处理器,消息处理器的执行过程不会阻塞到消息的生产。 + +为了保证消息不丢失,内部采用了 buffer.RingUnbounded 作为缓冲区实现,并且消息分发器不提供 Close 方法。 +如果需要关闭消息分发器,可以通过 Expel 方法设置驱逐计划,当消息分发器中没有任何消息时,将会被释放。 +同时,也可以使用 UnExpel 方法取消驱逐计划。 + +为什么提供 Expel 和 UnExpel 方法: + - 在连接断开时,当需要执行一系列消息处理时,如果直接关闭消息分发器,可能会导致消息丢失。所以提供了 Expel 方法,可以在消息处理完成后再关闭消息分发器。 + - 当消息还未处理完成时连接重连,如果没有取消驱逐计划,可能会导致消息分发器被关闭。所以提供了 UnExpel 方法,可以在连接重连后取消驱逐计划。 +```go +type Dispatcher[P Producer, M Message[P]] struct { + buf *buffer.RingUnbounded[M] + uniques *haxmap.Map[string, struct{}] + handler Handler[P, M] + expel bool + mc int64 + pmc map[P]int64 + pmcF map[P]func(p P, dispatcher *Action[P, M]) + lock sync.RWMutex + name string + closedHandler atomic.Pointer[func(dispatcher *Action[P, M])] + abort chan struct{} +} +``` +### Manager `STRUCT` +消息分发器管理器 +```go +type Manager[P Producer, M Message[P]] struct { + handler Handler[P, M] + sys *Dispatcher[P, M] + dispatchers map[string]*Dispatcher[P, M] + member map[string]map[P]struct{} + curr map[P]*Dispatcher[P, M] + lock sync.RWMutex + w sync.WaitGroup + size int + closedHandler func(name string) + createdHandler func(name string) +} +``` +### Message `INTERFACE` + +```go +type Message[P comparable] interface { + GetProducer() P +} +``` +### Producer `INTERFACE` + +```go +type Producer interface { + comparable +} +``` diff --git a/server/internal/logger/README.md b/server/internal/logger/README.md new file mode 100644 index 0000000..a4225b9 --- /dev/null +++ b/server/internal/logger/README.md @@ -0,0 +1,48 @@ +# Logger + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Ants](#ants)|暂无描述... +|`STRUCT`|[GNet](#gnet)|暂无描述... + +
+ + +*** +## 详情信息 +### Ants `STRUCT` + +```go +type Ants struct{} +``` +#### func (*Ants) Printf(format string, args ...interface {}) +*** +### GNet `STRUCT` + +```go +type GNet struct{} +``` +#### func (*GNet) Debugf(format string, args ...interface {}) +*** +#### func (*GNet) Infof(format string, args ...interface {}) +*** +#### func (*GNet) Warnf(format string, args ...interface {}) +*** +#### func (*GNet) Errorf(format string, args ...interface {}) +*** +#### func (*GNet) Fatalf(format string, args ...interface {}) +*** diff --git a/server/lockstep/README.md b/server/lockstep/README.md index cbeb096..5be645f 100644 --- a/server/lockstep/README.md +++ b/server/lockstep/README.md @@ -1,9 +1,156 @@ -# Lockstep [`锁步(帧)同步`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/lockstep#Lockstep) +# Lockstep + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/server/lockstep) -> 它是一个不限制网络类型的实现,仅需要对应连接实现 [`lockstep.Client`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/lockstep#Client) 接口即可。 +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 -该包提供了一个并发安全的锁步(帧)同步实现,其中内置了频率设置、帧上限、序列化、初始帧、追帧等功能。可使用其来快速构建和管理锁步(帧)同步。 -锁步(帧)同步是一种特殊的同步,它可以并发安全地将数据同步到底层连接。 \ No newline at end of file +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewLockstep](#NewLockstep)|创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回 +|[WithFrameLimit](#WithFrameLimit)|通过特定逻辑帧上限创建锁步(帧)同步组件 +|[WithFrameRate](#WithFrameRate)|通过特定逻辑帧率创建锁步(帧)同步组件 +|[WithSerialization](#WithSerialization)|通过特定的序列化方式将每一帧的数据进行序列化 +|[WithInitFrame](#WithInitFrame)|通过特定的初始帧创建锁步(帧)同步组件 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`INTERFACE`|[Client](#client)|帧同步客户端接口定义 +|`STRUCT`|[StoppedEventHandle](#stoppedeventhandle)|暂无描述... +|`STRUCT`|[Lockstep](#lockstep)|锁步(帧)同步默认实现 +|`STRUCT`|[Option](#option)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewLockstep(options ...Option[ClientID, Command]) *Lockstep[ClientID, Command] + +> 创建一个锁步(帧)同步默认实现的组件(Lockstep)进行返回 + +
+查看 / 收起单元测试 + + +```go + +func TestNewLockstep(t *testing.T) { + ls := lockstep.NewLockstep[string, int](lockstep.WithInitFrame[string, int](1)) + ls.JoinClient(&Cli{id: "player_1"}) + ls.JoinClient(&Cli{id: "player_2"}) + count := 0 + ls.StartBroadcast() + endChan := make(chan bool) + go func() { + for { + ls.AddCommand(random.Int(1, 9999)) + count++ + if count >= 10 { + break + } + time.Sleep(time.Millisecond * time.Duration(random.Int(10, 200))) + } + ls.StopBroadcast() + endChan <- true + }() + <-endChan + time.Sleep(time.Second) + fmt.Println("end") +} + +``` + + +
+ + +*** +#### func WithFrameLimit(frameLimit int64) Option[ClientID, Command] + +> 通过特定逻辑帧上限创建锁步(帧)同步组件 +> - 当达到上限时将停止广播 + +*** +#### func WithFrameRate(frameRate int64) Option[ClientID, Command] + +> 通过特定逻辑帧率创建锁步(帧)同步组件 +> - 默认情况下为 15/s + +*** +#### func WithSerialization(handle func (frame int64, commands []Command) []byte) Option[ClientID, Command] + +> 通过特定的序列化方式将每一帧的数据进行序列化 +> +> - 默认情况下为将被序列化为以下结构体的JSON字符串 +> +> type Frame struct { +> Frame int `json:"frame"` +> Commands []Command `json:"commands"` +> } + +*** +#### func WithInitFrame(initFrame int64) Option[ClientID, Command] + +> 通过特定的初始帧创建锁步(帧)同步组件 +> - 默认情况下为 0,即第一帧索引为 0 + +*** +### Client `INTERFACE` +帧同步客户端接口定义 + - 客户端应该具备ID及写入数据包的实现 +```go +type Client[ID comparable] interface { + GetID() ID + Write(packet []byte, callback ...func(err error)) +} +``` +### StoppedEventHandle `STRUCT` + +```go +type StoppedEventHandle[ClientID comparable, Command any] func(lockstep *Lockstep[ClientID, Command]) +``` +### Lockstep `STRUCT` +锁步(帧)同步默认实现 + - 支持最大帧上限 WithFrameLimit + - 自定逻辑帧频率,默认为每秒15帧(帧/66ms) WithFrameRate + - 自定帧序列化方式 WithSerialization + - 从特定帧开始追帧 + - 兼容各种基于TCP/UDP/Unix的网络类型,可通过客户端实现其他网络类型同步 +```go +type Lockstep[ClientID comparable, Command any] struct { + running bool + runningLock sync.RWMutex + initFrame int64 + frameRate int64 + frameLimit int64 + serialization func(frame int64, commands []Command) []byte + clients map[ClientID]Client[ClientID] + clientFrame map[ClientID]int64 + clientLock sync.RWMutex + currentFrame int64 + currentCommands []Command + currentFrameLock sync.RWMutex + frameCache map[int64][]byte + frameCacheLock sync.RWMutex + ticker *time.Ticker + lockstepStoppedEventHandles []StoppedEventHandle[ClientID, Command] +} +``` +### Option `STRUCT` + +```go +type Option[ClientID comparable, Command any] func(lockstep *Lockstep[ClientID, Command]) +``` diff --git a/server/router/README.md b/server/router/README.md new file mode 100644 index 0000000..2c28968 --- /dev/null +++ b/server/router/README.md @@ -0,0 +1,171 @@ +# Router + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewMultistage](#NewMultistage)|创建一个支持多级分类的路由器 +|[WithRouteTrim](#WithRouteTrim)|路由修剪选项 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[MultistageBind](#multistagebind)|多级分类路由绑定函数 +|`STRUCT`|[Multistage](#multistage)|支持多级分类的路由器 +|`STRUCT`|[MultistageOption](#multistageoption)|路由器选项 + +
+ + +*** +## 详情信息 +#### func NewMultistage(options ...MultistageOption[HandleFunc]) *Multistage[HandleFunc] + +> 创建一个支持多级分类的路由器 + +示例代码: +```go + +func ExampleNewMultistage() { + router.NewMultistage[func()]() +} + +``` + +*** +#### func WithRouteTrim(handle func (route any) any) MultistageOption[HandleFunc] + +> 路由修剪选项 +> - 将在路由注册前对路由进行对应处理 + +*** +### MultistageBind `STRUCT` +多级分类路由绑定函数 +```go +type MultistageBind[HandleFunc any] func(HandleFunc) +``` +#### func (MultistageBind) Bind(handleFunc HandleFunc) +> 将处理函数绑定到预设的路由中 +*** +### Multistage `STRUCT` +支持多级分类的路由器 +```go +type Multistage[HandleFunc any] struct { + routes map[any]HandleFunc + subs map[any]*Multistage[HandleFunc] + tag any + trim func(route any) any +} +``` +#### func (*Multistage) Register(routes ...any) MultistageBind[HandleFunc] +> 注册路由是结合 Sub 和 Route 的快捷方式,用于一次性注册多级路由 +> - 该函数将返回一个注册函数,可通过调用其将路由绑定到特定处理函数,例如:router.Register("a", "b").Bind(onExec()) +示例代码: +```go + +func ExampleMultistage_Register() { + r := router.NewMultistage[func()]() + r.Register("System", "Network", "Ping")(func() { + }) +} + +``` + +*** +#### func (*Multistage) Route(route any, handleFunc HandleFunc) +> 为特定路由绑定处理函数,被绑定的处理函数将可以通过 Match 函数进行匹配 +示例代码: +```go + +func ExampleMultistage_Route() { + r := router.NewMultistage[func()]() + r.Route("ServerTime", func() { + }) +} + +``` + +*** +#### func (*Multistage) Match(routes ...any) HandleFunc +> 匹配已绑定处理函数的路由,返回处理函数 +> - 如果未找到将会返回空指针 +示例代码: +```go + +func ExampleMultistage_Match() { + r := router.NewMultistage[func()]() + r.Route("ServerTime", func() { + }) + r.Register("System", "Network", "Ping").Bind(func() { + }) + r.Match("ServerTime")() + r.Match("System", "Network", "Ping")() +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestMultistage_Match(t *testing.T) { + r := router.NewMultistage[func()]() + r.Sub("System").Route("Heartbeat", func() { + fmt.Println("Heartbeat") + }) + r.Route("ServerTime", func() { + fmt.Println("ServerTime") + }) + r.Register("System", "Network", "Ping")(func() { + fmt.Println("Ping") + }) + r.Register("System", "Network", "Echo").Bind(onEcho) + r.Match("System", "Heartbeat")() + r.Match("ServerTime")() + r.Match("System", "Network", "Ping")() + r.Match("System", "Network", "Echo")() + fmt.Println(r.Match("None") == nil) +} + +``` + + +
+ + +*** +#### func (*Multistage) Sub(route any) *Multistage[HandleFunc] +> 获取子路由器 +示例代码: +```go + +func ExampleMultistage_Sub() { + r := router.NewMultistage[func()]() + r.Sub("System").Route("Heartbeat", func() { + }) +} + +``` + +*** +### MultistageOption `STRUCT` +路由器选项 +```go +type MultistageOption[HandleFunc any] func(multistage *Multistage[HandleFunc]) +``` diff --git a/server/writeloop/README.md b/server/writeloop/README.md index 799c9e7..73dfc13 100644 --- a/server/writeloop/README.md +++ b/server/writeloop/README.md @@ -1,41 +1,63 @@ -# WriteLoop +# Writeloop -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -该包提供了一个并发安全的写循环实现。开发者可以使用它来快速构建和管理写入操作。 +暂无介绍... -写循环是一种特殊的循环,它可以并发安全地将数据写入到底层连接。写循环在 `Minotaur` 中是一个泛型类型,可以处理任意类型的消息。 -## Unbounded [`基于无界缓冲区的写循环`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Unbounded) +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 -基于无界缓冲区的写循环实现,它可以处理任意数量的消息。它使用 [`Pool`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/concurrent#Pool) 来管理消息对象,使用 [`Unbounded`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/buffer#Unbounded) 来管理消息队列。 -> [`Unbounded`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Unbounded) 使用了 [`Pool`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/concurrent#Pool) 和 [`Unbounded`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/buffer#Unbounded) 进行实现。 -> 通过 [`Pool`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/concurrent#Pool) 创建的消息对象无需手动释放,它会在写循环处理完消息后自动回收。 +> 包级函数定义 -## Channel [`基于 chan 的写循环`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Channel) +|函数名称|描述 +|:--|:-- +|[NewChannel](#NewChannel)|创建基于 Channel 的写循环 +|[NewUnbounded](#NewUnbounded)|创建写循环 -基于 [`chan`](https://pkg.go.dev/builtin#chan) 的写循环实现,拥有极高的吞吐量。它使用 [`Pool`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/concurrent#Pool) 来管理消息对象,使用 [`Channel`](https://pkg.go.dev/builtin#chan) 来管理消息队列。 -## 如何选择 -- [`Unbounded`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Unbounded) 适用于消息体较小、消息量庞大、消息处理时间较慢的场景,由于没有消息数量限制,所以它可以处理任意数量的消息,但是它的吞吐量较低,并且会占用大量的内存。 -- [`Channel`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Channel) 适用于消息体较大、消息量较小、消息处理时间较快的场景,由于使用了 [`Channel`](https://pkg.go.dev/builtin#chan) 来管理消息队列,所以它的吞吐量较高,但是它的消息数量是有限制的,如果消息数量超过了限制,那么写循环会阻塞,造成无法及时响应消息(例如心跳中断等)。 +> 类型定义 -> 通常来说,建议使用 [`Channel`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Channel) 来实现写循环,因为它的吞吐量更高,并且绝大多数情况是很难达到阻塞的。 -### 使用示例 -> 该示例由 [`Unbounded`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Unbounded) 实现,[`Channel`](https://pkg.go.dev/github.com/kercylan98/minotaur/server/writeloop#Channel) 的使用方法与之类似。 +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Channel](#channel)|基于 chan 的写循环,与 Unbounded 相同,但是使用 Channel 实现 +|`STRUCT`|[Unbounded](#unbounded)|写循环 +|`INTERFACE`|[WriteLoop](#writeloop)|暂无描述... +
+ + +*** +## 详情信息 +#### func NewChannel(pool *hub.ObjectPool[Message], channelSize int, writeHandler func (message Message) error, errorHandler func (err any)) *Channel[Message] + +> 创建基于 Channel 的写循环 +> - pool 用于管理 Message 对象的缓冲池,在创建 Message 对象时也应该使用该缓冲池,以便复用 Message 对象。 Channel 会在写入完成后将 Message 对象放回缓冲池 +> - channelSize Channel 的大小 +> - writeHandler 写入处理函数 +> - errorHandler 错误处理函数 +> +> 传入 writeHandler 的消息对象是从 Channel 中获取的,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 + +*** +#### func NewUnbounded(pool *hub.ObjectPool[Message], writeHandler func (message Message) error, errorHandler func (err any)) *Unbounded[Message] + +> 创建写循环 +> - pool 用于管理 Message 对象的缓冲池,在创建 Message 对象时也应该使用该缓冲池,以便复用 Message 对象。 Unbounded 会在写入完成后将 Message 对象放回缓冲池 +> - writeHandler 写入处理函数 +> - errorHandler 错误处理函数 +> +> 传入 writeHandler 的消息对象是从 pool 中获取的,并且在 writeHandler 执行完成后会被放回 pool 中,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 + +示例代码: ```go -package main -import ( - "fmt" - "github.com/kercylan98/minotaur/server/writeloop" - "github.com/kercylan98/minotaur/utils/hub" -) - -func main() { - pool := concurrent.NewPool[Message](func() *Message { +func ExampleNewUnbounded() { + pool := hub.NewObjectPool[Message](func() *Message { return &Message{} }, func(data *Message) { data.ID = 0 @@ -49,16 +71,157 @@ func main() { }, func(err any) { fmt.Println(err) }) - for i := 0; i < 10; i++ { m := pool.Get() m.ID = i wl.Put(m) } - wait.Wait() wl.Close() } + ``` -在这个示例中,我们创建了一个写循环,然后将一些消息放入写循环。每个消息都会被 `writeHandle` 函数处理,如果在处理过程中发生错误,`errorHandle` 函数会被调用。在使用完写循环后,我们需要关闭它。 \ No newline at end of file +
+查看 / 收起单元测试 + + +```go + +func TestNewUnbounded(t *testing.T) { + wl := writeloop.NewUnbounded(wp, func(message *Message) error { + t.Log(message.ID) + return nil + }, func(err any) { + t.Log(err) + }) + assert.NotNil(t, wl) + wl.Close() +} + +``` + + +
+ + +*** +### Channel `STRUCT` +基于 chan 的写循环,与 Unbounded 相同,但是使用 Channel 实现 +```go +type Channel[T any] struct { + c chan T +} +``` +#### func (*Channel) Put(message T) +> 将数据放入写循环,message 应该来源于 hub.ObjectPool +*** +#### func (*Channel) Close() +> 关闭写循环 +*** +### Unbounded `STRUCT` +写循环 + - 用于将数据并发安全的写入到底层连接 +```go +type Unbounded[Message any] struct { + buf *buffer.Unbounded[Message] +} +``` +#### func (*Unbounded) Put(message Message) +> 将数据放入写循环,message 应该来源于 hub.ObjectPool +
+查看 / 收起单元测试 + + +```go + +func TestUnbounded_Put(t *testing.T) { + wl := writeloop.NewUnbounded(wp, func(message *Message) error { + t.Log(message.ID) + return nil + }, func(err any) { + t.Log(err) + }) + assert.NotNil(t, wl) + for i := 0; i < 100; i++ { + m := wp.Get() + m.ID = i + wl.Put(m) + } + wl.Close() +} + +``` + + +
+ + +
+查看 / 收起基准测试 + + +```go + +func BenchmarkUnbounded_Put(b *testing.B) { + wl := writeloop.NewUnbounded(wp, func(message *Message) error { + return nil + }, nil) + defer func() { + wl.Close() + }() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + wl.Put(wp.Get()) + } + }) + b.StopTimer() +} + +``` + + +
+ + +*** +#### func (*Unbounded) Close() +> 关闭写循环 +
+查看 / 收起单元测试 + + +```go + +func TestUnbounded_Close(t *testing.T) { + wl := writeloop.NewUnbounded(wp, func(message *Message) error { + t.Log(message.ID) + return nil + }, func(err any) { + t.Log(err) + }) + assert.NotNil(t, wl) + for i := 0; i < 100; i++ { + m := wp.Get() + m.ID = i + wl.Put(m) + } + wl.Close() +} + +``` + + +
+ + +*** +### WriteLoop `INTERFACE` + +```go +type WriteLoop[Message any] interface { + Put(message Message) + Close() +} +``` diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 0000000..b421677 --- /dev/null +++ b/utils/README.md @@ -0,0 +1,18 @@ +# Utils + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +utils 旨在提供一组用于处理通用功能的函数和数据结构。该包旨在简化通用功能的实现,并提供一致的接口和易于使用的功能。 +主要特性: + - 通用功能:utils 包支持处理各种通用功能,如字符串处理、日期时间操作和文件操作等。您可以使用这些功能来解决各种通用问题,并提高代码的复用性和可维护性。 + - 数据结构:该包提供了一系列通用的数据结构,如栈、队列、链表和哈希表等。这些数据结构可以用于存储和操作各种类型的数据,并提供高效的访问和操作能力。 + - 算法实现:utils 包还提供了一些常用的算法实现,如排序算法、搜索算法和图算法等。这些算法可以用于解决各种问题,并提供高效的计算和处理能力。 + - 工具函数:该包还提供了一些通用的工具函数,如字符串处理、日期时间操作和文件操作等。这些工具函数可以帮助您简化代码编写,处理文本数据,操作日期时间,读写文件等。 + + + + + + +*** diff --git a/utils/aoi/README.md b/utils/aoi/README.md index f7263ca..e2c7429 100644 --- a/utils/aoi/README.md +++ b/utils/aoi/README.md @@ -1,10 +1,99 @@ -# AOI (Area of Interest) +# Aoi -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/aoi) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -AOI (Area of Interest) 是一种常见的游戏服务器技术,用于处理大量玩家在同一空间内的交互问题。在 `Minotaur` 中,我们提供了一个基于 Go 语言的 AOI 实现。 +aoi 提供了一种有效的方法来处理 AOI(Area of Interest)问题。 -## TwoDimensional [`二维AOI`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/aoi#TwoDimensional) +AOI 问题是在大规模多人在线游戏中常见的问题,它涉及到确定哪些对象对玩家来说是“感兴趣的”, +也就是说,哪些对象在玩家的视野范围内。 -[`TwoDimensional`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/aoi#TwoDimensional) -是一个二维 AOI 的实现。它提供了添加实体、删除实体、刷新实体、获取焦点实体等方法。每个实体需要实现 [`TwoDimensionalEntity`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/aoi#TwoDimensionalEntity) 接口,该接口包含了获取实体 ID、获取实体坐标、获取实体视野半径等方法。 \ No newline at end of file +这个包提供了一种数据结构和一些方法来有效地解决这个问题。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewTwoDimensional](#NewTwoDimensional)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[TwoDimensional](#twodimensional)|暂无描述... +|`INTERFACE`|[TwoDimensionalEntity](#twodimensionalentity)|基于2D定义的AOI对象功能接口 +|`STRUCT`|[EntityJoinVisionEventHandle](#entityjoinvisioneventhandle)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewTwoDimensional(width int, height int, areaWidth int, areaHeight int) *TwoDimensional[EID, PosType, E] + + +
+查看 / 收起单元测试 + + +```go + +func TestNewTwoDimensional(t *testing.T) { + aoiTW := aoi.NewTwoDimensional[int64, float64, *Ent](10000, 10000, 100, 100) + start := time.Now() + for i := 0; i < 50000; i++ { + aoiTW.AddEntity(&Ent{guid: int64(i), pos: geometry.NewPoint[float64](float64(random.Int64(0, 10000)), float64(random.Int64(0, 10000))), vision: 200}) + } + fmt.Println("添加耗时:", time.Since(start)) + start = time.Now() + aoiTW.SetSize(10100, 10100) + fmt.Println("重设大小耗时:", time.Since(start)) +} + +``` + + +
+ + +*** +### TwoDimensional `STRUCT` + +```go +type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]] struct { + *event[EID, PosType, E] + rw sync.RWMutex + width float64 + height float64 + areaWidth float64 + areaHeight float64 + areaWidthLimit int + areaHeightLimit int + areas [][]map[EID]E + focus map[EID]map[EID]E + repartitionQueue []func() +} +``` +### TwoDimensionalEntity `INTERFACE` +基于2D定义的AOI对象功能接口 + - AOI 对象提供了 AOI 系统中常用的属性,诸如位置坐标和视野范围等 +```go +type TwoDimensionalEntity[EID generic.Basic, PosType generic.SignedNumber] interface { + GetTwoDimensionalEntityID() EID + GetVision() float64 + GetPosition() geometry.Point[PosType] +} +``` +### EntityJoinVisionEventHandle `STRUCT` + +```go +type EntityJoinVisionEventHandle[EID generic.Basic, PosType generic.SignedNumber, E TwoDimensionalEntity[EID, PosType]] func(entity E) +``` diff --git a/utils/arrangement/README.md b/utils/arrangement/README.md index 33bb0f4..93dcdb3 100644 --- a/utils/arrangement/README.md +++ b/utils/arrangement/README.md @@ -1,17 +1,194 @@ # Arrangement -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/arrangement) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -`Arrangement`包提供了一种灵活的方式来管理和操作编排区域。它包含了一些用于处理编排区域和编排选项的函数和类型。 +arrangement 包提供了一些有用的函数来处理数组的排列。 -## Area [`编排区域`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/arrangement#Area) -`Area`类型代表一个编排区域,它包含了一些方法,如`GetAreaInfo`、`GetItems`、`IsAllow`、`IsConflict`、`GetConflictItems`和`GetScore`,这些方法可以用来获取区域信息、获取区域中的所有成员、检查一个成员是否可以被添加到该区域中、检查一个成员是否会造成冲突、获取与一个成员产生冲突的所有其他成员以及获取该区域的评估分数。 +更多的详细信息和使用示例,可以参考每个函数的文档。 -## Option [`编排选项`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/arrangement#Option) -`Option`类型代表一个编排选项,它是一个函数,可以用来修改编排的行为。例如,`WithRetryThreshold`、`WithConstraintHandle`和`WithConflictHandle`函数可以用来设置编排时的重试阈值、约束处理函数和冲突处理函数。 -## AreaOption [`编排区域选项`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/arrangement#AreaOption) -`AreaOption`类型代表一个编排区域选项,它是一个函数,可以用来修改编排区域的行为。例如,`WithAreaConstraint`、`WithAreaConflict`和`WithAreaEvaluate`函数可以用来设置编排区域的约束条件、冲突条件和评估函数。 +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 -## 示例代码 -[点击查看](./arrangement_test.go) \ No newline at end of file + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[WithAreaConstraint](#WithAreaConstraint)|设置编排区域的约束条件 +|[WithAreaConflict](#WithAreaConflict)|设置编排区域的冲突条件,冲突处理函数需要返回造成冲突的成员列表 +|[WithAreaEvaluate](#WithAreaEvaluate)|设置编排区域的评估函数 +|[NewArrangement](#NewArrangement)|创建一个新的编排 +|[WithItemFixed](#WithItemFixed)|设置成员的固定编排区域 +|[WithItemPriority](#WithItemPriority)|设置成员的优先级 +|[WithItemNotAllow](#WithItemNotAllow)|设置成员不允许的编排区域 +|[WithRetryThreshold](#WithRetryThreshold)|设置编排时的重试阈值 +|[WithConstraintHandle](#WithConstraintHandle)|设置编排时触发约束时的处理函数 +|[WithConflictHandle](#WithConflictHandle)|设置编排时触发冲突时的处理函数 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Area](#area)|编排区域 +|`STRUCT`|[AreaOption](#areaoption)|编排区域选项 +|`STRUCT`|[AreaConstraintHandle](#areaconstrainthandle)|暂无描述... +|`STRUCT`|[Arrangement](#arrangement)|用于针对多条数据进行合理编排的数据结构 +|`STRUCT`|[Editor](#editor)|提供了大量辅助函数的编辑器 +|`INTERFACE`|[Item](#item)|编排成员 +|`STRUCT`|[ItemOption](#itemoption)|编排成员选项 +|`STRUCT`|[ItemFixedAreaHandle](#itemfixedareahandle)|暂无描述... +|`STRUCT`|[Option](#option)|编排选项 +|`STRUCT`|[ConstraintHandle](#constrainthandle)|暂无描述... + +
+ + +*** +## 详情信息 +#### func WithAreaConstraint(constraint AreaConstraintHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] + +> 设置编排区域的约束条件 +> - 该约束用于判断一个成员是否可以被添加到该编排区域中 +> - 与 WithAreaConflict 不同的是,约束通常用于非成员关系导致的硬性约束,例如:成员的等级过滤、成员的性别等 + +*** +#### func WithAreaConflict(conflict AreaConflictHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] + +> 设置编排区域的冲突条件,冲突处理函数需要返回造成冲突的成员列表 +> - 该冲突用于判断一个成员是否可以被添加到该编排区域中 +> - 与 WithAreaConstraint 不同的是,冲突通常用于成员关系导致的软性约束,例如:成员的职业唯一性、成员的种族唯一性等 + +*** +#### func WithAreaEvaluate(evaluate AreaEvaluateHandle[ID, AreaInfo]) AreaOption[ID, AreaInfo] + +> 设置编排区域的评估函数 +> - 该评估函数将影响成员被编入区域的优先级 + +*** +#### func NewArrangement(options ...Option[ID, AreaInfo]) *Arrangement[ID, AreaInfo] + +> 创建一个新的编排 + +*** +#### func WithItemFixed(matcher ItemFixedAreaHandle[AreaInfo]) ItemOption[ID, AreaInfo] + +> 设置成员的固定编排区域 + +*** +#### func WithItemPriority(priority ItemPriorityHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] + +> 设置成员的优先级 + +*** +#### func WithItemNotAllow(verify ItemNotAllowVerifyHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] + +> 设置成员不允许的编排区域 + +*** +#### func WithRetryThreshold(threshold int) Option[ID, AreaInfo] + +> 设置编排时的重试阈值 +> - 当每一轮编排结束任有成员未被编排时,将会进行下一轮编排,直到编排次数达到该阈值 +> - 默认的阈值为 10 次 + +*** +#### func WithConstraintHandle(handle ConstraintHandle[ID, AreaInfo]) Option[ID, AreaInfo] + +> 设置编排时触发约束时的处理函数 +> - 当约束条件触发时,将会调用该函数。如果无法在该函数中处理约束,应该继续返回 err,尝试进行下一层的约束处理 +> - 当该函数的返回值为 nil 时,表示约束已经被处理,将会命中当前的编排区域 +> - 当所有的约束处理函数都无法处理约束时,将会进入下一个编排区域的尝试,如果均无法完成,将会将该成员加入到编排队列的末端,等待下一次编排 +> +> 有意思的是,硬性约束应该永远是无解的,而当需要进行一些打破规则的操作时,则可以透过该函数传入的 editor 进行操作 + +*** +#### func WithConflictHandle(handle ConflictHandle[ID, AreaInfo]) Option[ID, AreaInfo] + +> 设置编排时触发冲突时的处理函数 +> - 当冲突条件触发时,将会调用该函数。如果无法在该函数中处理冲突,应该继续返回这一批成员,尝试进行下一层的冲突处理 +> - 当该函数的返回值长度为 0 时,表示冲突已经被处理,将会命中当前的编排区域 +> - 当所有的冲突处理函数都无法处理冲突时,将会进入下一个编排区域的尝试,如果均无法完成,将会将该成员加入到编排队列的末端,等待下一次编排 + +*** +### Area `STRUCT` +编排区域 +```go +type Area[ID comparable, AreaInfo any] struct { + info AreaInfo + items map[ID]Item[ID] + constraints []AreaConstraintHandle[ID, AreaInfo] + conflicts []AreaConflictHandle[ID, AreaInfo] + evaluate AreaEvaluateHandle[ID, AreaInfo] +} +``` +### AreaOption `STRUCT` +编排区域选项 +```go +type AreaOption[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo]) +``` +### AreaConstraintHandle `STRUCT` + +```go +type AreaConstraintHandle[ID comparable, AreaInfo any] func(area *Area[ID, AreaInfo], item Item[ID]) error +``` +### Arrangement `STRUCT` +用于针对多条数据进行合理编排的数据结构 + - 我不知道这个数据结构的具体用途,但是我觉得这个数据结构应该是有用的 + - 目前我能想到的用途只有我的过往经历:排课 + - 如果是在游戏领域,或许适用于多人小队匹配编排等类似情况 +```go +type Arrangement[ID comparable, AreaInfo any] struct { + areas []*Area[ID, AreaInfo] + items map[ID]Item[ID] + fixed map[ID]ItemFixedAreaHandle[AreaInfo] + priority map[ID][]ItemPriorityHandle[ID, AreaInfo] + itemNotAllow map[ID][]ItemNotAllowVerifyHandle[ID, AreaInfo] + threshold int + constraintHandles []ConstraintHandle[ID, AreaInfo] + conflictHandles []ConflictHandle[ID, AreaInfo] +} +``` +### Editor `STRUCT` +提供了大量辅助函数的编辑器 +```go +type Editor[ID comparable, AreaInfo any] struct { + a *Arrangement[ID, AreaInfo] + pending []Item[ID] + fails []Item[ID] + falls map[ID]struct{} + retryCount int +} +``` +### Item `INTERFACE` +编排成员 +```go +type Item[ID comparable] interface { + GetID() ID + Equal(item Item[ID]) bool +} +``` +### ItemOption `STRUCT` +编排成员选项 +```go +type ItemOption[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo], item Item[ID]) +``` +### ItemFixedAreaHandle `STRUCT` + +```go +type ItemFixedAreaHandle[AreaInfo any] func(areaInfo AreaInfo) bool +``` +### Option `STRUCT` +编排选项 +```go +type Option[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo]) +``` +### ConstraintHandle `STRUCT` + +```go +type ConstraintHandle[ID comparable, AreaInfo any] func(editor *Editor[ID, AreaInfo], area *Area[ID, AreaInfo], item Item[ID], err error) error +``` diff --git a/utils/buffer/README.md b/utils/buffer/README.md index 461eb1f..b56d19e 100644 --- a/utils/buffer/README.md +++ b/utils/buffer/README.md @@ -1,71 +1,304 @@ # Buffer -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/buffer) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -该包提供了多种缓冲区实现,包括环形缓冲区和无界缓冲区。开发者可以使用它来快速构建和管理缓冲区。 +buffer 提供了缓冲区相关的实用程序。 -## Ring [`环形缓冲区`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/buffer#Ring) +包括创建、读取和写入缓冲区的函数。 -环形缓冲区是一种特殊的缓冲区,它的头尾是相连的。当缓冲区满时,新的元素会覆盖旧的元素。环形缓冲区在 `Minotaur` 中是一个泛型类型,可以容纳任意类型的元素。 +这个包还提供了一个无界缓冲区的实现,可以在不使用额外 goroutine 的情况下实现无界缓冲区。 -## Unbounded [`无界缓冲区`](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/buffer#Unbounded) +无界缓冲区的所有方法都是线程安全的,除了用于同步的互斥锁外,不会阻塞任何东西。 -该缓冲区来源于 gRPC 的实现,用于在不使用额外 goroutine 的情况下实现无界缓冲区。无界缓冲区是一种特殊的缓冲区,它的大小可以动态扩展,不会出现溢出的情况。无界缓冲区在 `Minotaur` 中也是一个泛型类型,可以容纳任意类型的元素。 -### 使用示例 +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewRing](#NewRing)|创建一个并发不安全的环形缓冲区 +|[NewRingUnbounded](#NewRingUnbounded)|创建一个并发安全的基于环形缓冲区实现的无界缓冲区 +|[NewUnbounded](#NewUnbounded)|创建一个无界缓冲区 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Ring](#ring)|环形缓冲区 +|`STRUCT`|[RingUnbounded](#ringunbounded)|基于环形缓冲区实现的无界缓冲区 +|`STRUCT`|[Unbounded](#unbounded)|是无界缓冲区的实现 + +
+ + +*** +## 详情信息 +#### func NewRing(initSize ...int) *Ring[T] + +> 创建一个并发不安全的环形缓冲区 +> - initSize: 初始容量 +> +> 当初始容量小于 2 或未设置时,将会使用默认容量 2 + +
+查看 / 收起单元测试 -环形缓冲区: ```go -package main -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/buffer" -) - -func main() { - ring := buffer.NewRing[int](5) - for i := 0; i < 5; i++ { - ring.Write(i) - } - - for i := 0; i < 5; i++ { - v, _ := ring.Read() - fmt.Println(v) // 0 1 2 3 4 - } +func TestNewRing(t *testing.T) { + ring := buffer.NewRing[int]() + for i := 0; i < 100; i++ { + ring.Write(i) + t.Log(ring.Read()) + } } + ``` -无界缓冲区: + +
+ + +*** +#### func NewRingUnbounded(bufferSize int) *RingUnbounded[T] + +> 创建一个并发安全的基于环形缓冲区实现的无界缓冲区 + +*** +#### func NewUnbounded() *Unbounded[V] + +> 创建一个无界缓冲区 +> - generateNil: 生成空值的函数,该函数仅需始终返回 nil 即可 +> +> 该缓冲区来源于 gRPC 的实现,用于在不使用额外 goroutine 的情况下实现无界缓冲区 +> - 该缓冲区的所有方法都是线程安全的,除了用于同步的互斥锁外,不会阻塞任何东西 + +*** +### Ring `STRUCT` +环形缓冲区 +```go +type Ring[T any] struct { + buf []T + initSize int + size int + r int + w int +} +``` +#### func (*Ring) Read() T, error +> 读取数据 +
+查看 / 收起基准测试 + ```go -package main -import ( - "fmt" - "github.com/kercylan98/minotaur/utils/buffer" -) - -func main() { - unbounded := buffer.NewUnbounded[int]() - for i := 0; i < 10; i++ { - unbounded.Put(i) - } - - for { - select { - case v, ok := <-unbounded.Get(): - if !ok { - return - } - unbounded.Load() - fmt.Println(v) // 0 1 2 3 4 5 6 7 8 9 - if v == 9 { - unbounded.Close() - return - } - } - } +func BenchmarkRing_Read(b *testing.B) { + ring := buffer.NewRing[int](1024) + for i := 0; i < b.N; i++ { + ring.Write(i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = ring.Read() + } } -``` \ No newline at end of file + +``` + + +
+ + +*** +#### func (*Ring) ReadAll() []T +> 读取所有数据 +*** +#### func (*Ring) Peek() (t T, err error) +> 查看数据 +*** +#### func (*Ring) Write(v T) +> 写入数据 +
+查看 / 收起基准测试 + + +```go + +func BenchmarkRing_Write(b *testing.B) { + ring := buffer.NewRing[int](1024) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ring.Write(i) + } +} + +``` + + +
+ + +*** +#### func (*Ring) IsEmpty() bool +> 是否为空 +*** +#### func (*Ring) Cap() int +> 返回缓冲区容量 +*** +#### func (*Ring) Len() int +> 返回缓冲区长度 +*** +#### func (*Ring) Reset() +> 重置缓冲区 +*** +### RingUnbounded `STRUCT` +基于环形缓冲区实现的无界缓冲区 +```go +type RingUnbounded[T any] struct { + ring *Ring[T] + rrm sync.Mutex + cond *sync.Cond + rc chan T + closed bool + closedMutex sync.RWMutex + closedSignal chan struct{} +} +``` +#### func (*RingUnbounded) Write(v T) +> 写入数据 +
+查看 / 收起基准测试 + + +```go + +func BenchmarkRingUnbounded_Write(b *testing.B) { + ring := buffer.NewRingUnbounded[int](1024 * 16) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ring.Write(i) + } +} + +``` + + +
+ + +*** +#### func (*RingUnbounded) Read() chan T +> 读取数据 +
+查看 / 收起基准测试 + + +```go + +func BenchmarkRingUnbounded_Read(b *testing.B) { + ring := buffer.NewRingUnbounded[int](1024 * 16) + for i := 0; i < b.N; i++ { + ring.Write(i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + <-ring.Read() + } +} + +``` + + +
+ + +*** +#### func (*RingUnbounded) Closed() bool +> 判断缓冲区是否已关闭 +*** +#### func (*RingUnbounded) Close() chan struct {} +> 关闭缓冲区,关闭后将不再接收新数据,但是已有数据仍然可以读取 +
+查看 / 收起单元测试 + + +```go + +func TestRingUnbounded_Close(t *testing.T) { + ring := buffer.NewRingUnbounded[int](1024 * 16) + for i := 0; i < 100; i++ { + ring.Write(i) + } + t.Log("write done") + ring.Close() + t.Log("close done") + for v := range ring.Read() { + ring.Write(v) + t.Log(v) + } + t.Log("read done") +} + +``` + + +
+ + +*** +### Unbounded `STRUCT` +是无界缓冲区的实现 +```go +type Unbounded[V any] struct { + c chan V + closed bool + mu sync.Mutex + backlog []V +} +``` +#### func (*Unbounded) Put(t V) +> 将数据放入缓冲区 +*** +#### func (*Unbounded) Load() +> 将缓冲区中的数据发送到读取通道中,如果缓冲区中没有数据,则不会发送 +> - 在每次 Get 后都应该执行该函数 +*** +#### func (*Unbounded) Get() chan V +> 获取读取通道 +
+查看 / 收起单元测试 + + +```go + +func TestUnbounded_Get(t *testing.T) { + ub := buffer.NewUnbounded[int]() + for i := 0; i < 100; i++ { + ub.Put(i + 1) + fmt.Println(<-ub.Get()) + ub.Load() + } +} + +``` + + +
+ + +*** +#### func (*Unbounded) Close() +> 关闭 +*** +#### func (*Unbounded) IsClosed() bool +> 是否已关闭 +*** diff --git a/utils/buffer/ring_benchmark_test.go b/utils/buffer/ring_benchmark_test.go index 4d9d11c..ee1f6af 100644 --- a/utils/buffer/ring_benchmark_test.go +++ b/utils/buffer/ring_benchmark_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func BenchmarkRingWrite(b *testing.B) { +func BenchmarkRing_Write(b *testing.B) { ring := buffer.NewRing[int](1024) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -13,7 +13,7 @@ func BenchmarkRingWrite(b *testing.B) { } } -func BenchmarkRingRead(b *testing.B) { +func BenchmarkRing_Read(b *testing.B) { ring := buffer.NewRing[int](1024) for i := 0; i < b.N; i++ { ring.Write(i) diff --git a/utils/collection/README.md b/utils/collection/README.md new file mode 100644 index 0000000..1fa259f --- /dev/null +++ b/utils/collection/README.md @@ -0,0 +1,5423 @@ +# Collection + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +collection 用于对 input 和 map 操作的工具函数 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[CloneSlice](#CloneSlice)|克隆切片,该函数是 slices.Clone 的快捷方式 +|[CloneMap](#CloneMap)|克隆 map +|[CloneSliceN](#CloneSliceN)|克隆 slice 为 n 个切片进行返回 +|[CloneMapN](#CloneMapN)|克隆 map 为 n 个 map 进行返回 +|[CloneSlices](#CloneSlices)|克隆多个切片 +|[CloneMaps](#CloneMaps)|克隆多个 map +|[InSlice](#InSlice)|检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 +|[InComparableSlice](#InComparableSlice)|检查 v 是否被包含在 slice 中 +|[AllInSlice](#AllInSlice)|检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配 +|[AllInComparableSlice](#AllInComparableSlice)|检查 values 中的所有元素是否均被包含在 slice 中 +|[AnyInSlice](#AnyInSlice)|检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配 +|[AnyInComparableSlice](#AnyInComparableSlice)|检查 values 中的任意一个元素是否被包含在 slice 中 +|[InSlices](#InSlices)|通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 +|[InComparableSlices](#InComparableSlices)|通过将多个切片合并后检查 v 是否被包含在 slices 中 +|[AllInSlices](#AllInSlices)|通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 +|[AllInComparableSlices](#AllInComparableSlices)|通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中 +|[AnyInSlices](#AnyInSlices)|通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 +|[AnyInComparableSlices](#AnyInComparableSlices)|通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中 +|[InAllSlices](#InAllSlices)|检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 +|[InAllComparableSlices](#InAllComparableSlices)|检查 v 是否被包含在 slices 的每一项元素中 +|[AnyInAllSlices](#AnyInAllSlices)|检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配 +|[AnyInAllComparableSlices](#AnyInAllComparableSlices)|检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素 +|[KeyInMap](#KeyInMap)|检查 m 中是否包含特定 key +|[ValueInMap](#ValueInMap)|检查 m 中是否包含特定 value,当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配 +|[AllKeyInMap](#AllKeyInMap)|检查 m 中是否包含 keys 中所有的元素 +|[AllValueInMap](#AllValueInMap)|检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 +|[AnyKeyInMap](#AnyKeyInMap)|检查 m 中是否包含 keys 中任意一个元素 +|[AnyValueInMap](#AnyValueInMap)|检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 +|[AllKeyInMaps](#AllKeyInMaps)|检查 maps 中的每一个元素是否均包含 keys 中所有的元素 +|[AllValueInMaps](#AllValueInMaps)|检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 +|[AnyKeyInMaps](#AnyKeyInMaps)|检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中 +|[AnyValueInMaps](#AnyValueInMaps)|检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 +|[KeyInAllMaps](#KeyInAllMaps)|检查 key 是否被包含在 maps 的每一个元素中 +|[AnyKeyInAllMaps](#AnyKeyInAllMaps)|检查 maps 中的每一个元素是否均包含 keys 中任意一个元素 +|[ConvertSliceToAny](#ConvertSliceToAny)|将切片转换为任意类型的切片 +|[ConvertSliceToIndexMap](#ConvertSliceToIndexMap)|将切片转换为索引为键的映射 +|[ConvertSliceToIndexOnlyMap](#ConvertSliceToIndexOnlyMap)|将切片转换为索引为键的映射 +|[ConvertSliceToMap](#ConvertSliceToMap)|将切片转换为值为键的映射 +|[ConvertSliceToBoolMap](#ConvertSliceToBoolMap)|将切片转换为值为键的映射 +|[ConvertMapKeysToSlice](#ConvertMapKeysToSlice)|将映射的键转换为切片 +|[ConvertMapValuesToSlice](#ConvertMapValuesToSlice)|将映射的值转换为切片 +|[InvertMap](#InvertMap)|将映射的键和值互换 +|[ConvertMapValuesToBool](#ConvertMapValuesToBool)|将映射的值转换为布尔值 +|[ReverseSlice](#ReverseSlice)|将切片反转 +|[ClearSlice](#ClearSlice)|清空切片 +|[ClearMap](#ClearMap)|清空 map +|[DropSliceByIndices](#DropSliceByIndices)|删除切片中特定索引的元素 +|[DropSliceByCondition](#DropSliceByCondition)|删除切片中符合条件的元素 +|[DropSliceOverlappingElements](#DropSliceOverlappingElements)|删除切片中与另一个切片重叠的元素 +|[DeduplicateSliceInPlace](#DeduplicateSliceInPlace)|去除切片中的重复元素 +|[DeduplicateSlice](#DeduplicateSlice)|去除切片中的重复元素,返回新切片 +|[DeduplicateSliceInPlaceWithCompare](#DeduplicateSliceInPlaceWithCompare)|去除切片中的重复元素,使用自定义的比较函数 +|[DeduplicateSliceWithCompare](#DeduplicateSliceWithCompare)|去除切片中的重复元素,使用自定义的比较函数,返回新的切片 +|[FilterOutByIndices](#FilterOutByIndices)|过滤切片中特定索引的元素,返回过滤后的切片 +|[FilterOutByCondition](#FilterOutByCondition)|过滤切片中符合条件的元素,返回过滤后的切片 +|[FilterOutByKey](#FilterOutByKey)|过滤 map 中特定的 key,返回过滤后的 map +|[FilterOutByValue](#FilterOutByValue)|过滤 map 中特定的 value,返回过滤后的 map +|[FilterOutByKeys](#FilterOutByKeys)|过滤 map 中多个 key,返回过滤后的 map +|[FilterOutByValues](#FilterOutByValues)|过滤 map 中多个 values,返回过滤后的 map +|[FilterOutByMap](#FilterOutByMap)|过滤 map 中符合条件的元素,返回过滤后的 map +|[FindLoopedNextInSlice](#FindLoopedNextInSlice)|返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始 +|[FindLoopedPrevInSlice](#FindLoopedPrevInSlice)|返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始 +|[FindCombinationsInSliceByRange](#FindCombinationsInSliceByRange)|获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 +|[FindFirstOrDefaultInSlice](#FindFirstOrDefaultInSlice)|判断切片中是否存在元素,返回第一个元素,不存在则返回默认值 +|[FindOrDefaultInSlice](#FindOrDefaultInSlice)|判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 +|[FindOrDefaultInComparableSlice](#FindOrDefaultInComparableSlice)|判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 +|[FindInSlice](#FindInSlice)|判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 +|[FindIndexInSlice](#FindIndexInSlice)|判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 +|[FindInComparableSlice](#FindInComparableSlice)|判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 +|[FindIndexInComparableSlice](#FindIndexInComparableSlice)|判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 +|[FindMinimumInComparableSlice](#FindMinimumInComparableSlice)|获取切片中的最小值 +|[FindMinimumInSlice](#FindMinimumInSlice)|获取切片中的最小值 +|[FindMaximumInComparableSlice](#FindMaximumInComparableSlice)|获取切片中的最大值 +|[FindMaximumInSlice](#FindMaximumInSlice)|获取切片中的最大值 +|[FindMin2MaxInComparableSlice](#FindMin2MaxInComparableSlice)|获取切片中的最小值和最大值 +|[FindMin2MaxInSlice](#FindMin2MaxInSlice)|获取切片中的最小值和最大值 +|[FindMinFromComparableMap](#FindMinFromComparableMap)|获取 map 中的最小值 +|[FindMinFromMap](#FindMinFromMap)|获取 map 中的最小值 +|[FindMaxFromComparableMap](#FindMaxFromComparableMap)|获取 map 中的最大值 +|[FindMaxFromMap](#FindMaxFromMap)|获取 map 中的最大值 +|[FindMin2MaxFromComparableMap](#FindMin2MaxFromComparableMap)|获取 map 中的最小值和最大值 +|[FindMin2MaxFromMap](#FindMin2MaxFromMap)|获取 map 中的最小值和最大值 +|[SwapSlice](#SwapSlice)|将切片中的两个元素进行交换 +|[MappingFromSlice](#MappingFromSlice)|将切片中的元素进行转换 +|[MappingFromMap](#MappingFromMap)|将 map 中的元素进行转换 +|[MergeSlices](#MergeSlices)|合并切片 +|[MergeMaps](#MergeMaps)|合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key +|[MergeMapsWithSkip](#MergeMapsWithSkip)|合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会被跳过 +|[ChooseRandomSliceElementRepeatN](#ChooseRandomSliceElementRepeatN)|返回 slice 中的 n 个可重复随机元素 +|[ChooseRandomIndexRepeatN](#ChooseRandomIndexRepeatN)|返回 slice 中的 n 个可重复随机元素的索引 +|[ChooseRandomSliceElement](#ChooseRandomSliceElement)|返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值 +|[ChooseRandomIndex](#ChooseRandomIndex)|返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1 +|[ChooseRandomSliceElementN](#ChooseRandomSliceElementN)|返回 slice 中的 n 个不可重复的随机元素 +|[ChooseRandomIndexN](#ChooseRandomIndexN)|获取切片中的 n 个随机元素的索引 +|[ChooseRandomMapKeyRepeatN](#ChooseRandomMapKeyRepeatN)|获取 map 中的 n 个随机 key,允许重复 +|[ChooseRandomMapValueRepeatN](#ChooseRandomMapValueRepeatN)|获取 map 中的 n 个随机 n,允许重复 +|[ChooseRandomMapKeyAndValueRepeatN](#ChooseRandomMapKeyAndValueRepeatN)|获取 map 中的 n 个随机 key 和 v,允许重复 +|[ChooseRandomMapKey](#ChooseRandomMapKey)|获取 map 中的随机 key +|[ChooseRandomMapValue](#ChooseRandomMapValue)|获取 map 中的随机 value +|[ChooseRandomMapKeyN](#ChooseRandomMapKeyN)|获取 map 中的 inputN 个随机 key +|[ChooseRandomMapValueN](#ChooseRandomMapValueN)|获取 map 中的 n 个随机 value +|[ChooseRandomMapKeyAndValue](#ChooseRandomMapKeyAndValue)|获取 map 中的随机 key 和 v +|[ChooseRandomMapKeyAndValueN](#ChooseRandomMapKeyAndValueN)|获取 map 中的 inputN 个随机 key 和 v +|[DescBy](#DescBy)|返回降序比较结果 +|[AscBy](#AscBy)|返回升序比较结果 +|[Desc](#Desc)|对切片进行降序排序 +|[DescByClone](#DescByClone)|对切片进行降序排序,返回排序后的切片 +|[Asc](#Asc)|对切片进行升序排序 +|[AscByClone](#AscByClone)|对切片进行升序排序,返回排序后的切片 +|[Shuffle](#Shuffle)|对切片进行随机排序 +|[ShuffleByClone](#ShuffleByClone)|对切片进行随机排序,返回排序后的切片 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[ComparisonHandler](#comparisonhandler)|暂无描述... +|`STRUCT`|[OrderedValueGetter](#orderedvaluegetter)|暂无描述... + +
+ + +*** +## 详情信息 +#### func CloneSlice(slice S) S + +> 克隆切片,该函数是 slices.Clone 的快捷方式 + +示例代码: +```go + +func ExampleCloneSlice() { + var slice = []int{1, 2, 3} + var result = collection.CloneSlice(slice) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 CloneMap(m M) M + +> 克隆 map + +示例代码: +```go + +func ExampleCloneMap() { + var m = map[int]int{1: 1, 2: 2, 3: 3} + var result = collection.CloneMap(m) + fmt.Println(len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 CloneSliceN(slice S, n int) []S + +> 克隆 slice 为 n 个切片进行返回 + +示例代码: +```go + +func ExampleCloneSliceN() { + var slice = []int{1, 2, 3} + var result = collection.CloneSliceN(slice, 2) + fmt.Println(len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 CloneMapN(m M, n int) []M + +> 克隆 map 为 n 个 map 进行返回 + +示例代码: +```go + +func ExampleCloneMapN() { + var m = map[int]int{1: 1, 2: 2, 3: 3} + var result = collection.CloneMapN(m, 2) + fmt.Println(len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 CloneSlices(slices ...S) []S + +> 克隆多个切片 + +示例代码: +```go + +func ExampleCloneSlices() { + var slice1 = []int{1, 2, 3} + var slice2 = []int{1, 2, 3} + var result = collection.CloneSlices(slice1, slice2) + fmt.Println(len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 CloneMaps(maps ...M) []M + +> 克隆多个 map + +示例代码: +```go + +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)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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") + } + } + } + }) + } +} + +``` + + +
+ + +*** +#### func InSlice(slice S, v V, handler ComparisonHandler[V]) bool + +> 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配 + +示例代码: +```go + +func ExampleInSlice() { + result := collection.InSlice([]int{1, 2, 3}, 2, func(source, target int) bool { + return source == target + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 InComparableSlice(slice S, v V) bool + +> 检查 v 是否被包含在 slice 中 + +示例代码: +```go + +func ExampleInComparableSlice() { + result := collection.InComparableSlice([]int{1, 2, 3}, 2) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllInSlice(slice S, values []V, handler ComparisonHandler[V]) bool + +> 检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配 +> - 在所有 values 中的元素都被包含在 slice 中时,返回 true +> - 当 values 长度为 0 或为 nil 时,将返回 true + +示例代码: +```go + +func ExampleAllInSlice() { + result := collection.AllInSlice([]int{1, 2, 3}, []int{1, 2}, func(source, target int) bool { + return source == target + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllInComparableSlice(slice S, values []V) bool + +> 检查 values 中的所有元素是否均被包含在 slice 中 +> - 在所有 values 中的元素都被包含在 slice 中时,返回 true +> - 当 values 长度为 0 或为 nil 时,将返回 true + +示例代码: +```go + +func ExampleAllInComparableSlice() { + result := collection.AllInComparableSlice([]int{1, 2, 3}, []int{1, 2}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyInSlice(slice S, values []V, handler ComparisonHandler[V]) bool + +> 检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配 +> - 当 values 中的任意一个元素被包含在 slice 中时,返回 true + +示例代码: +```go + +func ExampleAnyInSlice() { + result := collection.AnyInSlice([]int{1, 2, 3}, []int{1, 2}, func(source, target int) bool { + return source == target + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyInComparableSlice(slice S, values []V) bool + +> 检查 values 中的任意一个元素是否被包含在 slice 中 +> - 当 values 中的任意一个元素被包含在 slice 中时,返回 true + +示例代码: +```go + +func ExampleAnyInComparableSlice() { + result := collection.AnyInComparableSlice([]int{1, 2, 3}, []int{1, 2}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 InSlices(slices []S, v V, handler ComparisonHandler[V]) bool + +> 通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 +> - 当传入的 v 被包含在 slices 的任一成员中时,返回 true + +示例代码: +```go + +func ExampleInSlices() { + result := collection.InSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2, func(source, target int) bool { + return source == target + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 InComparableSlices(slices []S, v V) bool + +> 通过将多个切片合并后检查 v 是否被包含在 slices 中 +> - 当传入的 v 被包含在 slices 的任一成员中时,返回 true + +示例代码: +```go + +func ExampleInComparableSlices() { + result := collection.InComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool + +> 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 +> - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllInComparableSlices(slices []S, values []V) bool + +> 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中 +> - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true + +示例代码: +```go + +func ExampleAllInComparableSlices() { + result := collection.AllInComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyInSlices(slices []S, values []V, handler ComparisonHandler[V]) bool + +> 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配 +> - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyInComparableSlices(slices []S, values []V) bool + +> 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中 +> - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true + +示例代码: +```go + +func ExampleAnyInComparableSlices() { + result := collection.AnyInComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 InAllSlices(slices []S, v V, handler ComparisonHandler[V]) bool + +> 检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配 +> - 当 v 被包含在 slices 的每一项元素中时,返回 true + +示例代码: +```go + +func ExampleInAllSlices() { + result := collection.InAllSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2, func(source, target int) bool { + return source == target + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 InAllComparableSlices(slices []S, v V) bool + +> 检查 v 是否被包含在 slices 的每一项元素中 +> - 当 v 被包含在 slices 的每一项元素中时,返回 true + +示例代码: +```go + +func ExampleInAllComparableSlices() { + result := collection.InAllComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyInAllSlices(slices []S, values []V, handler ComparisonHandler[V]) bool + +> 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配 +> - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyInAllComparableSlices(slices []S, values []V) bool + +> 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素 +> - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true + +示例代码: +```go + +func ExampleAnyInAllComparableSlices() { + result := collection.AnyInAllComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 KeyInMap(m M, key K) bool + +> 检查 m 中是否包含特定 key + +示例代码: +```go + +func ExampleKeyInMap() { + result := collection.KeyInMap(map[string]int{"a": 1, "b": 2}, "a") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ValueInMap(m M, value V, handler ComparisonHandler[V]) bool + +> 检查 m 中是否包含特定 value,当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配 + +示例代码: +```go + +func ExampleValueInMap() { + result := collection.ValueInMap(map[string]int{"a": 1, "b": 2}, 2, func(source, target int) bool { + return source == target + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllKeyInMap(m M, keys ...K) bool + +> 检查 m 中是否包含 keys 中所有的元素 + +示例代码: +```go + +func ExampleAllKeyInMap() { + result := collection.AllKeyInMap(map[string]int{"a": 1, "b": 2}, "a", "b") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllValueInMap(m M, values []V, handler ComparisonHandler[V]) bool + +> 检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyKeyInMap(m M, keys ...K) bool + +> 检查 m 中是否包含 keys 中任意一个元素 + +示例代码: +```go + +func ExampleAnyKeyInMap() { + result := collection.AnyKeyInMap(map[string]int{"a": 1, "b": 2}, "a", "b") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyValueInMap(m M, values []V, handler ComparisonHandler[V]) bool + +> 检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllKeyInMaps(maps []M, keys ...K) bool + +> 检查 maps 中的每一个元素是否均包含 keys 中所有的元素 + +示例代码: +```go + +func ExampleAllKeyInMaps() { + result := collection.AllKeyInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a", "b") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AllValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool + +> 检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyKeyInMaps(maps []M, keys ...K) bool + +> 检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中 +> - 当 keys 中的任意一个元素被包含在 maps 中的任意一个元素中时,返回 true + +示例代码: +```go + +func ExampleAnyKeyInMaps() { + result := collection.AnyKeyInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a", "b") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyValueInMaps(maps []M, values []V, handler ComparisonHandler[V]) bool + +> 检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配 +> - 当 maps 中的任意一个元素包含 value 中的任意一个元素时,返回 true + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 KeyInAllMaps(maps []M, key K) bool + +> 检查 key 是否被包含在 maps 的每一个元素中 + +示例代码: +```go + +func ExampleKeyInAllMaps() { + result := collection.KeyInAllMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 AnyKeyInAllMaps(maps []M, keys []K) bool + +> 检查 maps 中的每一个元素是否均包含 keys 中任意一个元素 +> - 当 maps 中的每一个元素均包含 keys 中任意一个元素时,返回 true + +示例代码: +```go + +func ExampleAnyKeyInAllMaps() { + result := collection.AnyKeyInAllMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []string{"a"}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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) + } + }) + } +} + +``` + + +
+ + +*** +#### func ConvertSliceToAny(s S) []any + +> 将切片转换为任意类型的切片 + +示例代码: +```go + +func ExampleConvertSliceToAny() { + result := collection.ConvertSliceToAny([]int{1, 2, 3}) + fmt.Println(reflect.TypeOf(result).String(), len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ConvertSliceToIndexMap(s S) map[int]V + +> 将切片转换为索引为键的映射 + +示例代码: +```go + +func ExampleConvertSliceToIndexMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToIndexMap(slice) + for i, v := range slice { + fmt.Println(result[i], v) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ConvertSliceToIndexOnlyMap(s S) map[int]struct {} + +> 将切片转换为索引为键的映射 + +示例代码: +```go + +func ExampleConvertSliceToIndexOnlyMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToIndexOnlyMap(slice) + expected := map[int]bool{0: true, 1: true, 2: true} + for k := range result { + fmt.Println(expected[k]) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestConvertSliceToIndexOnlyMap(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]struct{} + }{{name: "TestConvertSliceToIndexOnlyMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{0: {}, 1: {}, 2: {}}}, {name: "TestConvertSliceToIndexOnlyMap_Empty", input: []int{}, expected: map[int]struct{}{}}, {name: "TestConvertSliceToIndexOnlyMap_Nil", input: nil, expected: nil}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ConvertSliceToIndexOnlyMap(c.input) + if len(actual) != len(c.expected) { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + for k, v := range actual { + if _, ok := c.expected[k]; !ok { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + if v != struct{}{} { + t.Errorf("expected: %v, actual: %v", c.expected, actual) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func ConvertSliceToMap(s S) map[V]struct {} + +> 将切片转换为值为键的映射 + +示例代码: +```go + +func ExampleConvertSliceToMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToMap(slice) + fmt.Println(collection.AllKeyInMap(result, slice...)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ConvertSliceToBoolMap(s S) map[V]bool + +> 将切片转换为值为键的映射 + +示例代码: +```go + +func ExampleConvertSliceToBoolMap() { + slice := []int{1, 2, 3} + result := collection.ConvertSliceToBoolMap(slice) + for _, v := range slice { + fmt.Println(v, result[v]) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ConvertMapKeysToSlice(m M) []K + +> 将映射的键转换为切片 + +示例代码: +```go + +func ExampleConvertMapKeysToSlice() { + result := collection.ConvertMapKeysToSlice(map[int]int{1: 1, 2: 2, 3: 3}) + for i, v := range result { + fmt.Println(i, v) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ConvertMapValuesToSlice(m M) []V + +> 将映射的值转换为切片 + +示例代码: +```go + +func ExampleConvertMapValuesToSlice() { + result := collection.ConvertMapValuesToSlice(map[int]int{1: 1, 2: 2, 3: 3}) + expected := map[int]bool{1: true, 2: true, 3: true} + for _, v := range result { + fmt.Println(expected[v]) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 InvertMap(m M) N + +> 将映射的键和值互换 + +示例代码: +```go + +func ExampleInvertMap() { + result := collection.InvertMap(map[int]string{1: "a", 2: "b", 3: "c"}) + fmt.Println(collection.AllKeyInMap(result, "a", "b", "c")) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ConvertMapValuesToBool(m M) N + +> 将映射的值转换为布尔值 + +示例代码: +```go + +func ExampleConvertMapValuesToBool() { + result := collection.ConvertMapValuesToBool(map[int]int{1: 1}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ReverseSlice(s *S) + +> 将切片反转 + +示例代码: +```go + +func ExampleReverseSlice() { + var s = []int{1, 2, 3} + collection.ReverseSlice(&s) + fmt.Println(s) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func ClearSlice(slice *S) + +> 清空切片 + +示例代码: +```go + +func ExampleClearSlice() { + slice := []int{1, 2, 3, 4, 5} + ClearSlice(&slice) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 ClearMap(m M) + +> 清空 map + +示例代码: +```go + +func ExampleClearMap() { + m := map[int]int{1: 1, 2: 2, 3: 3} + ClearMap(m) + fmt.Println(m) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 DropSliceByIndices(slice *S, indices ...int) + +> 删除切片中特定索引的元素 + +示例代码: +```go + +func ExampleDropSliceByIndices() { + slice := []int{1, 2, 3, 4, 5} + DropSliceByIndices(&slice, 1, 3) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 DropSliceByCondition(slice *S, condition func (v V) bool) + +> 删除切片中符合条件的元素 +> - condition 的返回值为 true 时,将会删除该元素 + +示例代码: +```go + +func ExampleDropSliceByCondition() { + slice := []int{1, 2, 3, 4, 5} + DropSliceByCondition(&slice, func(v int) bool { + return v%2 == 0 + }) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 DropSliceOverlappingElements(slice *S, anotherSlice []V, comparisonHandler ComparisonHandler[V]) + +> 删除切片中与另一个切片重叠的元素 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func DeduplicateSliceInPlace(s *S) + +> 去除切片中的重复元素 + +示例代码: +```go + +func ExampleDeduplicateSliceInPlace() { + slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1} + DeduplicateSliceInPlace(&slice) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 DeduplicateSlice(s S) S + +> 去除切片中的重复元素,返回新切片 + +示例代码: +```go + +func ExampleDeduplicateSlice() { + slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1} + fmt.Println(DeduplicateSlice(slice)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 DeduplicateSliceInPlaceWithCompare(s *S, compare func (a V) bool) + +> 去除切片中的重复元素,使用自定义的比较函数 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 DeduplicateSliceWithCompare(s S, compare func (a V) bool) S + +> 去除切片中的重复元素,使用自定义的比较函数,返回新的切片 + +示例代码: +```go + +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 + })) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func FilterOutByIndices(slice S, indices ...int) S + +> 过滤切片中特定索引的元素,返回过滤后的切片 + +示例代码: +```go + +func ExampleFilterOutByIndices() { + var slice = []int{1, 2, 3, 4, 5} + var result = collection.FilterOutByIndices(slice, 1, 3) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FilterOutByCondition(slice S, condition func (v V) bool) S + +> 过滤切片中符合条件的元素,返回过滤后的切片 +> - condition 的返回值为 true 时,将会过滤掉该元素 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FilterOutByKey(m M, key K) M + +> 过滤 map 中特定的 key,返回过滤后的 map + +示例代码: +```go + +func ExampleFilterOutByKey() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result = collection.FilterOutByKey(m, "b") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FilterOutByValue(m M, value V, handler ComparisonHandler[V]) M + +> 过滤 map 中特定的 value,返回过滤后的 map + +示例代码: +```go + +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)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FilterOutByKeys(m M, keys ...K) M + +> 过滤 map 中多个 key,返回过滤后的 map + +示例代码: +```go + +func ExampleFilterOutByKeys() { + var m = map[string]int{"a": 1, "b": 2, "c": 3} + var result = collection.FilterOutByKeys(m, "a", "c") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FilterOutByValues(m M, values []V, handler ComparisonHandler[V]) M + +> 过滤 map 中多个 values,返回过滤后的 map + +示例代码: +```go + +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]) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FilterOutByMap(m M, condition func (k K, v V) bool) M + +> 过滤 map 中符合条件的元素,返回过滤后的 map +> - condition 的返回值为 true 时,将会过滤掉该元素 + +示例代码: +```go + +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) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func FindLoopedNextInSlice(slice S, i int) (next int, value V) + +> 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始 +> - 当 i 为负数时将返回第一个元素 + +示例代码: +```go + +func ExampleFindLoopedNextInSlice() { + next, v := collection.FindLoopedNextInSlice([]int{1, 2, 3}, 1) + fmt.Println(next, v) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FindLoopedPrevInSlice(slice S, i int) (prev int, value V) + +> 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始 +> - 当 i 为负数时将返回最后一个元素 + +示例代码: +```go + +func ExampleFindLoopedPrevInSlice() { + prev, v := collection.FindLoopedPrevInSlice([]int{1, 2, 3}, 1) + fmt.Println(prev, v) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FindCombinationsInSliceByRange(s S, minSize int, maxSize int) []S + +> 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内 + +示例代码: +```go + +func ExampleFindCombinationsInSliceByRange() { + result := collection.FindCombinationsInSliceByRange([]int{1, 2, 3}, 1, 2) + fmt.Println(len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FindFirstOrDefaultInSlice(slice S, defaultValue V) V + +> 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值 + +示例代码: +```go + +func ExampleFindFirstOrDefaultInSlice() { + result := collection.FindFirstOrDefaultInSlice([]int{1, 2, 3}, 0) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FindOrDefaultInSlice(slice S, defaultValue V, handler func (v V) bool) (t V) + +> 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 + +示例代码: +```go + +func ExampleFindOrDefaultInSlice() { + result := collection.FindOrDefaultInSlice([]int{1, 2, 3}, 0, func(v int) bool { + return v == 2 + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +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 FindOrDefaultInComparableSlice(slice S, v V, defaultValue V) (t V) + +> 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值 + +示例代码: +```go + +func ExampleFindOrDefaultInComparableSlice() { + result := collection.FindOrDefaultInComparableSlice([]int{1, 2, 3}, 2, 0) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindOrDefaultInComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindOrDefaultInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2}, {"TestFindOrDefaultInComparableSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindOrDefaultInComparableSlice(c.input, 2, 0) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindInSlice(slice S, handler func (v V) bool) (i int, t V) + +> 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 + +示例代码: +```go + +func ExampleFindInSlice() { + _, result := collection.FindInSlice([]int{1, 2, 3}, func(v int) bool { + return v == 2 + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindInSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindInSlice_NonEmptySlice", []int{1, 2, 3}, 2}, {"TestFindInSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + _, actual := collection.FindInSlice(c.input, func(v int) bool { + return v == 2 + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindIndexInSlice(slice S, handler func (v V) bool) int + +> 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 + +示例代码: +```go + +func ExampleFindIndexInSlice() { + result := collection.FindIndexInSlice([]int{1, 2, 3}, func(v int) bool { + return v == 2 + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindIndexInSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindIndexInSlice_NonEmptySlice", []int{1, 2, 3}, 1}, {"TestFindIndexInSlice_EmptySlice", []int{}, -1}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindIndexInSlice(c.input, func(v int) bool { + return v == 2 + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindInComparableSlice(slice S, v V) (i int, t V) + +> 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1 + +示例代码: +```go + +func ExampleFindInComparableSlice() { + index, result := collection.FindInComparableSlice([]int{1, 2, 3}, 2) + fmt.Println(index, result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindInComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2}, {"TestFindInComparableSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + _, actual := collection.FindInComparableSlice(c.input, 2) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindIndexInComparableSlice(slice S, v V) int + +> 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1 + +示例代码: +```go + +func ExampleFindIndexInComparableSlice() { + result := collection.FindIndexInComparableSlice([]int{1, 2, 3}, 2) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindIndexInComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindIndexInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1}, {"TestFindIndexInComparableSlice_EmptySlice", []int{}, -1}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindIndexInComparableSlice(c.input, 2) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMinimumInComparableSlice(slice S) (result V) + +> 获取切片中的最小值 + +示例代码: +```go + +func ExampleFindMinimumInComparableSlice() { + result := collection.FindMinimumInComparableSlice([]int{1, 2, 3}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMinimumInComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindMinimumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1}, {"TestFindMinimumInComparableSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMinimumInComparableSlice(c.input) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMinimumInSlice(slice S, handler OrderedValueGetter[V, N]) (result V) + +> 获取切片中的最小值 + +示例代码: +```go + +func ExampleFindMinimumInSlice() { + result := collection.FindMinimumInSlice([]int{1, 2, 3}, func(v int) int { + return v + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMinimumInSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindMinimumInSlice_NonEmptySlice", []int{1, 2, 3}, 1}, {"TestFindMinimumInSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMinimumInSlice(c.input, func(v int) int { + return v + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMaximumInComparableSlice(slice S) (result V) + +> 获取切片中的最大值 + +示例代码: +```go + +func ExampleFindMaximumInComparableSlice() { + result := collection.FindMaximumInComparableSlice([]int{1, 2, 3}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMaximumInComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindMaximumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 3}, {"TestFindMaximumInComparableSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMaximumInComparableSlice(c.input) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMaximumInSlice(slice S, handler OrderedValueGetter[V, N]) (result V) + +> 获取切片中的最大值 + +示例代码: +```go + +func ExampleFindMaximumInSlice() { + result := collection.FindMaximumInSlice([]int{1, 2, 3}, func(v int) int { + return v + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMaximumInSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestFindMaximumInSlice_NonEmptySlice", []int{1, 2, 3}, 3}, {"TestFindMaximumInSlice_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMaximumInSlice(c.input, func(v int) int { + return v + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMin2MaxInComparableSlice(slice S) (min V, max V) + +> 获取切片中的最小值和最大值 + +示例代码: +```go + +func ExampleFindMin2MaxInComparableSlice() { + minimum, maximum := collection.FindMin2MaxInComparableSlice([]int{1, 2, 3}) + fmt.Println(minimum, maximum) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMin2MaxInComparableSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expectedMin int + expectedMax int + }{{"TestFindMin2MaxInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3}, {"TestFindMin2MaxInComparableSlice_EmptySlice", []int{}, 0, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + minimum, maximum := collection.FindMin2MaxInComparableSlice(c.input) + if minimum != c.expectedMin || maximum != c.expectedMax { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMin2MaxInSlice(slice S, handler OrderedValueGetter[V, N]) (min V, max V) + +> 获取切片中的最小值和最大值 + +示例代码: +```go + +func ExampleFindMin2MaxInSlice() { + minimum, maximum := collection.FindMin2MaxInSlice([]int{1, 2, 3}, func(v int) int { + return v + }) + fmt.Println(minimum, maximum) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMin2MaxInSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expectedMin int + expectedMax int + }{{"TestFindMin2MaxInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3}, {"TestFindMin2MaxInSlice_EmptySlice", []int{}, 0, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + minimum, maximum := collection.FindMin2MaxInSlice(c.input, func(v int) int { + return v + }) + if minimum != c.expectedMin || maximum != c.expectedMax { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMinFromComparableMap(m M) (result V) + +> 获取 map 中的最小值 + +示例代码: +```go + +func ExampleFindMinFromComparableMap() { + result := collection.FindMinFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMinFromComparableMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected int + }{{"TestFindMinFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1}, {"TestFindMinFromComparableMap_EmptyMap", map[int]int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMinFromComparableMap(c.input) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMinFromMap(m M, handler OrderedValueGetter[V, N]) (result V) + +> 获取 map 中的最小值 + +示例代码: +```go + +func ExampleFindMinFromMap() { + result := collection.FindMinFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int { + return v + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMinFromMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected int + }{{"TestFindMinFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1}, {"TestFindMinFromMap_EmptyMap", map[int]int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMinFromMap(c.input, func(v int) int { + return v + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMaxFromComparableMap(m M) (result V) + +> 获取 map 中的最大值 + +示例代码: +```go + +func ExampleFindMaxFromComparableMap() { + result := collection.FindMaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMaxFromComparableMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected int + }{{"TestFindMaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3}, {"TestFindMaxFromComparableMap_EmptyMap", map[int]int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMaxFromComparableMap(c.input) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMaxFromMap(m M, handler OrderedValueGetter[V, N]) (result V) + +> 获取 map 中的最大值 + +示例代码: +```go + +func ExampleFindMaxFromMap() { + result := collection.FindMaxFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int { + return v + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMaxFromMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected int + }{{"TestFindMaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3}, {"TestFindMaxFromMap_EmptyMap", map[int]int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.FindMaxFromMap(c.input, func(v int) int { + return v + }) + if actual != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMin2MaxFromComparableMap(m M) (min V, max V) + +> 获取 map 中的最小值和最大值 + +示例代码: +```go + +func ExampleFindMin2MaxFromComparableMap() { + minimum, maximum := collection.FindMin2MaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3}) + fmt.Println(minimum, maximum) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMin2MaxFromComparableMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expectedMin int + expectedMax int + }{{"TestFindMin2MaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3}, {"TestFindMin2MaxFromComparableMap_EmptyMap", map[int]int{}, 0, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + minimum, maximum := collection.FindMin2MaxFromComparableMap(c.input) + if minimum != c.expectedMin || maximum != c.expectedMax { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func FindMin2MaxFromMap(m M) (min V, max V) + +> 获取 map 中的最小值和最大值 + +示例代码: +```go + +func ExampleFindMin2MaxFromMap() { + minimum, maximum := collection.FindMin2MaxFromMap(map[int]int{1: 1, 2: 2, 3: 3}) + fmt.Println(minimum, maximum) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestFindMin2MaxFromMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expectedMin int + expectedMax int + }{{"TestFindMin2MaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3}, {"TestFindMin2MaxFromMap_EmptyMap", map[int]int{}, 0, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + minimum, maximum := collection.FindMin2MaxFromMap(c.input) + if minimum != c.expectedMin || maximum != c.expectedMax { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func SwapSlice(slice *S, i int, j int) + +> 将切片中的两个元素进行交换 + +示例代码: +```go + +func ExampleSwapSlice() { + var s = []int{1, 2, 3} + collection.SwapSlice(&s, 0, 1) + fmt.Println(s) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestSwapSlice(t *testing.T) { + var cases = []struct { + name string + slice []int + i int + j int + expect []int + }{{"TestSwapSliceNonEmpty", []int{1, 2, 3}, 0, 1, []int{2, 1, 3}}, {"TestSwapSliceEmpty", []int{}, 0, 0, []int{}}, {"TestSwapSliceIndexOutOfBound", []int{1, 2, 3}, 0, 3, []int{1, 2, 3}}, {"TestSwapSliceNil", nil, 0, 0, nil}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + collection.SwapSlice(&c.slice, c.i, c.j) + for i, v := range c.slice { + if v != c.expect[i] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expect, c.slice, "the slice is not equal") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func MappingFromSlice(slice S, handler func (value V) N) NS + +> 将切片中的元素进行转换 + +示例代码: +```go + +func ExampleMappingFromSlice() { + result := collection.MappingFromSlice([]int{1, 2, 3}, func(value int) int { + return value + 1 + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestMappingFromSlice(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestMappingFromSlice_NonEmptySlice", []int{1, 2, 3}, []int{2, 3, 4}}, {"TestMappingFromSlice_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.MappingFromSlice[[]int, []int](c.input, func(value int) int { + return value + 1 + }) + if len(result) != len(c.expected) { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + for i := 0; i < len(result); i++ { + if result[i] != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func MappingFromMap(m M, handler func (value V) N) NM + +> 将 map 中的元素进行转换 + +示例代码: +```go + +func ExampleMappingFromMap() { + result := collection.MappingFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(value int) int { + return value + 1 + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestMappingFromMap(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]int + }{{"TestMappingFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 2, 2: 3, 3: 4}}, {"TestMappingFromMap_EmptyMap", map[int]int{}, map[int]int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.MappingFromMap[map[int]int, map[int]int](c.input, func(value int) int { + return value + 1 + }) + if len(result) != len(c.expected) { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + for k, v := range result { + if v != c.expected[k] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func MergeSlices(slices ...S) (result S) + +> 合并切片 + +示例代码: +```go + +func ExampleMergeSlices() { + fmt.Println(collection.MergeSlices([]int{1, 2, 3}, []int{4, 5, 6})) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestMergeSlices(t *testing.T) { + var cases = []struct { + name string + input [][]int + expected []int + }{{"TestMergeSlices_NonEmptySlice", [][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2, 3, 4, 5, 6}}, {"TestMergeSlices_EmptySlice", [][]int{{}, {}}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.MergeSlices(c.input...) + if len(result) != len(c.expected) { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + for i := 0; i < len(result); i++ { + if result[i] != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func MergeMaps(maps ...M) (result M) + +> 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key + +示例代码: +```go + +func ExampleMergeMaps() { + m := collection.MergeMaps(map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{4: 4, 5: 5, 6: 6}) + fmt.Println(len(m)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestMergeMaps(t *testing.T) { + var cases = []struct { + name string + input []map[int]int + expected int + }{{"TestMergeMaps_NonEmptyMap", []map[int]int{{1: 1, 2: 2, 3: 3}, {4: 4, 5: 5, 6: 6}}, 6}, {"TestMergeMaps_EmptyMap", []map[int]int{{}, {}}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.MergeMaps(c.input...) + if len(result) != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func MergeMapsWithSkip(maps ...M) (result M) + +> 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会被跳过 + +示例代码: +```go + +func ExampleMergeMapsWithSkip() { + m := collection.MergeMapsWithSkip(map[int]int{1: 1}, map[int]int{1: 2}) + fmt.Println(m[1]) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestMergeMapsWithSkip(t *testing.T) { + var cases = []struct { + name string + input []map[int]int + expected int + }{{"TestMergeMapsWithSkip_NonEmptyMap", []map[int]int{{1: 1}, {1: 2}}, 1}, {"TestMergeMapsWithSkip_EmptyMap", []map[int]int{{}, {}}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.MergeMapsWithSkip(c.input...) + if len(result) != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomSliceElementRepeatN(slice S, n int) (result []V) + +> 返回 slice 中的 n 个可重复随机元素 +> - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil + +示例代码: +```go + +func ExampleChooseRandomSliceElementRepeatN() { + result := collection.ChooseRandomSliceElementRepeatN([]int{1}, 10) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomSliceElementRepeatN(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestChooseRandomSliceElementRepeatN_NonEmptySlice", []int{1, 2, 3}, 3}, {"TestChooseRandomSliceElementRepeatN_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomSliceElementRepeatN(c.input, 3) + if len(result) != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomIndexRepeatN(slice S, n int) (result []int) + +> 返回 slice 中的 n 个可重复随机元素的索引 +> - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil + +示例代码: +```go + +func ExampleChooseRandomIndexRepeatN() { + result := collection.ChooseRandomIndexRepeatN([]int{1}, 10) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomIndexRepeatN(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestChooseRandomIndexRepeatN_NonEmptySlice", []int{1, 2, 3}, 3}, {"TestChooseRandomIndexRepeatN_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomIndexRepeatN(c.input, 3) + if len(result) != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomSliceElement(slice S) (v V) + +> 返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值 + +示例代码: +```go + +func ExampleChooseRandomSliceElement() { + result := collection.ChooseRandomSliceElement([]int{1}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomSliceElement(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]bool + }{{"TestChooseRandomSliceElement_NonEmptySlice", []int{1, 2, 3}, map[int]bool{1: true, 2: true, 3: true}}, {"TestChooseRandomSliceElement_EmptySlice", []int{}, map[int]bool{0: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomSliceElement(c.input) + if !c.expected[result] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomIndex(slice S) (index int) + +> 返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1 + +示例代码: +```go + +func ExampleChooseRandomIndex() { + result := collection.ChooseRandomIndex([]int{1}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomIndex(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]bool + }{{"TestChooseRandomIndex_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}}, {"TestChooseRandomIndex_EmptySlice", []int{}, map[int]bool{-1: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomIndex(c.input) + if !c.expected[result] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomSliceElementN(slice S, n int) (result []V) + +> 返回 slice 中的 n 个不可重复的随机元素 +> - 当 slice 长度为 0 或 n 大于 slice 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomSliceElementN() { + result := collection.ChooseRandomSliceElementN([]int{1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomSliceElementN(t *testing.T) { + var cases = []struct { + name string + input []int + }{{"TestChooseRandomSliceElementN_NonEmptySlice", []int{1, 2, 3}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := collection.ChooseRandomSliceElementN(c.input, 3) + if !collection.AllInComparableSlice(actual, c.input) { + t.Fatalf("%s failed, actual: %v, error: %s", c.name, actual, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomIndexN(slice S, n int) (result []int) + +> 获取切片中的 n 个随机元素的索引 +> - 如果 n 大于切片长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomIndexN() { + result := collection.ChooseRandomIndexN([]int{1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomIndexN(t *testing.T) { + var cases = []struct { + name string + input []int + expected map[int]bool + }{{"TestChooseRandomIndexN_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomIndexN(c.input, 3) + if !c.expected[result[0]] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapKeyRepeatN(m M, n int) (result []K) + +> 获取 map 中的 n 个随机 key,允许重复 +> - 如果 n 大于 map 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomMapKeyRepeatN() { + result := collection.ChooseRandomMapKeyRepeatN(map[int]int{1: 1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapKeyRepeatN(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapKeyRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomMapKeyRepeatN(c.input, 3) + if !c.expected[result[0]] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapValueRepeatN(m M, n int) (result []V) + +> 获取 map 中的 n 个随机 n,允许重复 +> - 如果 n 大于 map 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomMapValueRepeatN() { + result := collection.ChooseRandomMapValueRepeatN(map[int]int{1: 1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapValueRepeatN(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomMapValueRepeatN(c.input, 3) + if !c.expected[result[0]] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapKeyAndValueRepeatN(m M, n int) M + +> 获取 map 中的 n 个随机 key 和 v,允许重复 +> - 如果 n 大于 map 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomMapKeyAndValueRepeatN() { + result := collection.ChooseRandomMapKeyAndValueRepeatN(map[int]int{1: 1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapKeyAndValueRepeatN(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapKeyAndValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomMapKeyAndValueRepeatN(c.input, 3) + if !c.expected[result[1]] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapKey(m M) (k K) + +> 获取 map 中的随机 key + +示例代码: +```go + +func ExampleChooseRandomMapKey() { + result := collection.ChooseRandomMapKey(map[int]int{1: 1}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapKey(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}, {"TestChooseRandomMapKey_EmptyMap", map[int]int{}, map[int]bool{0: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomMapKey(c.input) + if !c.expected[result] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapValue(m M) (v V) + +> 获取 map 中的随机 value + +示例代码: +```go + +func ExampleChooseRandomMapValue() { + result := collection.ChooseRandomMapValue(map[int]int{1: 1}) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapValue(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}, {"TestChooseRandomMapValue_EmptyMap", map[int]int{}, map[int]bool{0: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomMapValue(c.input) + if !c.expected[result] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapKeyN(m M, n int) (result []K) + +> 获取 map 中的 inputN 个随机 key +> - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomMapKeyN() { + result := collection.ChooseRandomMapKeyN(map[int]int{1: 1}, 1) + fmt.Println(result) +} + +``` + +*** +#### func ChooseRandomMapValueN(m M, n int) (result []V) + +> 获取 map 中的 n 个随机 value +> - 如果 n 大于 map 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomMapValueN() { + result := collection.ChooseRandomMapValueN(map[int]int{1: 1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapValueN(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ChooseRandomMapValueN(c.input, 3) + if !c.expected[result[0]] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapKeyAndValue(m M) (k K, v V) + +> 获取 map 中的随机 key 和 v + +示例代码: +```go + +func ExampleChooseRandomMapKeyAndValue() { + k, v := collection.ChooseRandomMapKeyAndValue(map[int]int{1: 1}) + fmt.Println(k, v) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapKeyAndValue(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapKeyAndValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}, {"TestChooseRandomMapKeyAndValue_EmptyMap", map[int]int{}, map[int]bool{0: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + k, v := collection.ChooseRandomMapKeyAndValue(c.input) + if !c.expected[k] || !c.expected[v] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal") + } + }) + } +} + +``` + + +
+ + +*** +#### func ChooseRandomMapKeyAndValueN(m M, n int) M + +> 获取 map 中的 inputN 个随机 key 和 v +> - 如果 n 大于 map 长度或小于 0 时将会发生 panic + +示例代码: +```go + +func ExampleChooseRandomMapKeyAndValueN() { + result := collection.ChooseRandomMapKeyAndValueN(map[int]int{1: 1}, 1) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestChooseRandomMapKeyAndValueN(t *testing.T) { + var cases = []struct { + name string + input map[int]int + expected map[int]bool + }{{"TestChooseRandomMapKeyAndValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + kvm := collection.ChooseRandomMapKeyAndValueN(c.input, 1) + for k := range kvm { + if !c.expected[k] { + t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal") + } + } + }) + } +} + +``` + + +
+ + +*** +#### func DescBy(a Sort, b Sort) bool + +> 返回降序比较结果 + +示例代码: +```go + +func ExampleDescBy() { + var slice = []int{1, 2, 3} + sort.Slice(slice, func(i, j int) bool { + return collection.DescBy(slice[i], slice[j]) + }) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestDescBy(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestDescBy_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}}, {"TestDescBy_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + sort.Slice(c.input, func(i, j int) bool { + return collection.DescBy(c.input[i], c.input[j]) + }) + for i, v := range c.input { + if v != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func AscBy(a Sort, b Sort) bool + +> 返回升序比较结果 + +示例代码: +```go + +func ExampleAscBy() { + var slice = []int{1, 2, 3} + sort.Slice(slice, func(i, j int) bool { + return collection.AscBy(slice[i], slice[j]) + }) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestAscBy(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestAscBy_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}}, {"TestAscBy_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + sort.Slice(c.input, func(i, j int) bool { + return collection.AscBy(c.input[i], c.input[j]) + }) + for i, v := range c.input { + if v != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func Desc(slice *S, getter func (index int) Sort) + +> 对切片进行降序排序 + +示例代码: +```go + +func ExampleDesc() { + var slice = []int{1, 2, 3} + collection.Desc(&slice, func(index int) int { + return slice[index] + }) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestDesc(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestDesc_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}}, {"TestDesc_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + collection.Desc(&c.input, func(index int) int { + return c.input[index] + }) + for i, v := range c.input { + if v != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func DescByClone(slice S, getter func (index int) Sort) S + +> 对切片进行降序排序,返回排序后的切片 + +示例代码: +```go + +func ExampleDescByClone() { + var slice = []int{1, 2, 3} + result := collection.DescByClone(slice, func(index int) int { + return slice[index] + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestDescByClone(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestDescByClone_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}}, {"TestDescByClone_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.DescByClone(c.input, func(index int) int { + return c.input[index] + }) + for i, v := range result { + if v != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func Asc(slice *S, getter func (index int) Sort) + +> 对切片进行升序排序 + +示例代码: +```go + +func ExampleAsc() { + var slice = []int{1, 2, 3} + collection.Asc(&slice, func(index int) int { + return slice[index] + }) + fmt.Println(slice) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestAsc(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestAsc_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}}, {"TestAsc_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + collection.Asc(&c.input, func(index int) int { + return c.input[index] + }) + for i, v := range c.input { + if v != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func AscByClone(slice S, getter func (index int) Sort) S + +> 对切片进行升序排序,返回排序后的切片 + +示例代码: +```go + +func ExampleAscByClone() { + var slice = []int{1, 2, 3} + result := collection.AscByClone(slice, func(index int) int { + return slice[index] + }) + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestAscByClone(t *testing.T) { + var cases = []struct { + name string + input []int + expected []int + }{{"TestAscByClone_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}}, {"TestAscByClone_EmptySlice", []int{}, []int{}}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.AscByClone(c.input, func(index int) int { + return c.input[index] + }) + for i, v := range result { + if v != c.expected[i] { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result) + } + } + }) + } +} + +``` + + +
+ + +*** +#### func Shuffle(slice *S) + +> 对切片进行随机排序 + +示例代码: +```go + +func ExampleShuffle() { + var slice = []int{1, 2, 3} + collection.Shuffle(&slice) + fmt.Println(len(slice)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestShuffle(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestShuffle_NonEmptySlice", []int{1, 2, 3}, 3}, {"TestShuffle_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + collection.Shuffle(&c.input) + if len(c.input) != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input) + } + }) + } +} + +``` + + +
+ + +*** +#### func ShuffleByClone(slice S) S + +> 对切片进行随机排序,返回排序后的切片 + +示例代码: +```go + +func ExampleShuffleByClone() { + var slice = []int{1, 2, 3} + result := collection.ShuffleByClone(slice) + fmt.Println(len(result)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestShuffleByClone(t *testing.T) { + var cases = []struct { + name string + input []int + expected int + }{{"TestShuffleByClone_NonEmptySlice", []int{1, 2, 3}, 3}, {"TestShuffleByClone_EmptySlice", []int{}, 0}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := collection.ShuffleByClone(c.input) + if len(result) != c.expected { + t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result) + } + }) + } +} + +``` + + +
+ + +*** +### ComparisonHandler `STRUCT` + +```go +type ComparisonHandler[V any] func(source V) bool +``` +### OrderedValueGetter `STRUCT` + +```go +type OrderedValueGetter[V any, N generic.Ordered] func(v V) N +``` diff --git a/utils/collection/listings/README.md b/utils/collection/listings/README.md new file mode 100644 index 0000000..57dd1d8 --- /dev/null +++ b/utils/collection/listings/README.md @@ -0,0 +1,205 @@ +# Listings + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewMatrix](#NewMatrix)|创建一个新的 Matrix 实例。 +|[NewPagedSlice](#NewPagedSlice)|创建一个新的 PagedSlice 实例。 +|[NewPrioritySlice](#NewPrioritySlice)|创建一个优先级切片 +|[NewSyncSlice](#NewSyncSlice)|创建一个 SyncSlice + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Matrix](#matrix)|暂无描述... +|`STRUCT`|[PagedSlice](#pagedslice)|是一个高效的动态数组,它通过分页管理内存并减少频繁的内存分配来提高性能。 +|`STRUCT`|[PrioritySlice](#priorityslice)|是一个优先级切片 +|`STRUCT`|[SyncSlice](#syncslice)|是基于 sync.RWMutex 实现的线程安全的 slice + +
+ + +*** +## 详情信息 +#### func NewMatrix(dimensions ...int) *Matrix[V] + +> 创建一个新的 Matrix 实例。 + +*** +#### func NewPagedSlice(pageSize int) *PagedSlice[T] + +> 创建一个新的 PagedSlice 实例。 + +*** +#### func NewPrioritySlice(lengthAndCap ...int) *PrioritySlice[V] + +> 创建一个优先级切片 + +*** +#### func NewSyncSlice(length int, cap int) *SyncSlice[V] + +> 创建一个 SyncSlice + +*** +### Matrix `STRUCT` + +```go +type Matrix[V any] struct { + dimensions []int + data []V +} +``` +#### func (*Matrix) Get(index ...int) *V +> 获取矩阵中给定索引的元素。 +*** +#### func (*Matrix) Set(index []int, value V) +> 设置矩阵中给定索引的元素。 +*** +#### func (*Matrix) Dimensions() []int +> 返回矩阵的维度大小。 +*** +#### func (*Matrix) Clear() +> 清空矩阵。 +*** +### PagedSlice `STRUCT` +是一个高效的动态数组,它通过分页管理内存并减少频繁的内存分配来提高性能。 +```go +type PagedSlice[T any] struct { + pages [][]T + pageSize int + len int + lenLast int +} +``` +#### func (*PagedSlice) Add(value T) +> 添加一个元素到 PagedSlice 中。 +*** +#### func (*PagedSlice) Get(index int) *T +> 获取 PagedSlice 中给定索引的元素。 +*** +#### func (*PagedSlice) Set(index int, value T) +> 设置 PagedSlice 中给定索引的元素。 +*** +#### func (*PagedSlice) Len() int +> 返回 PagedSlice 中元素的数量。 +*** +#### func (*PagedSlice) Clear() +> 清空 PagedSlice。 +*** +#### func (*PagedSlice) Range(f func (index int, value T) bool) +> 迭代 PagedSlice 中的所有元素。 +*** +### PrioritySlice `STRUCT` +是一个优先级切片 +```go +type PrioritySlice[V any] struct { + items []*priorityItem[V] +} +``` +#### func (*PrioritySlice) Len() int +> 返回切片长度 +*** +#### func (*PrioritySlice) Cap() int +> 返回切片容量 +*** +#### func (*PrioritySlice) Clear() +> 清空切片 +*** +#### func (*PrioritySlice) Append(v V, p int) +> 添加元素 +
+查看 / 收起单元测试 + + +```go + +func TestPrioritySlice_Append(t *testing.T) { + var s = listings.NewPrioritySlice[string]() + s.Append("name_1", 2) + s.Append("name_2", 1) + fmt.Println(s) +} + +``` + + +
+ + +*** +#### func (*PrioritySlice) Appends(priority int, vs ...V) +> 添加元素 +*** +#### func (*PrioritySlice) Get(index int) V, int +> 获取元素 +*** +#### func (*PrioritySlice) GetValue(index int) V +> 获取元素值 +*** +#### func (*PrioritySlice) GetPriority(index int) int +> 获取元素优先级 +*** +#### func (*PrioritySlice) Set(index int, value V, priority int) +> 设置元素 +*** +#### func (*PrioritySlice) SetValue(index int, value V) +> 设置元素值 +*** +#### func (*PrioritySlice) SetPriority(index int, priority int) +> 设置元素优先级 +*** +#### func (*PrioritySlice) Action(action func (items []*priorityItem[V]) []*priorityItem[V]) +> 直接操作切片,如果返回值不为 nil,则替换切片 +*** +#### func (*PrioritySlice) Range(action func (index int, item *priorityItem[V]) bool) +> 遍历切片,如果返回值为 false,则停止遍历 +*** +#### func (*PrioritySlice) RangeValue(action func (index int, value V) bool) +> 遍历切片值,如果返回值为 false,则停止遍历 +*** +#### func (*PrioritySlice) RangePriority(action func (index int, priority int) bool) +> 遍历切片优先级,如果返回值为 false,则停止遍历 +*** +#### func (*PrioritySlice) Slice() []V +> 返回切片 +*** +#### func (*PrioritySlice) String() string +> 返回切片字符串 +*** +### SyncSlice `STRUCT` +是基于 sync.RWMutex 实现的线程安全的 slice +```go +type SyncSlice[V any] struct { + rw sync.RWMutex + data []V +} +``` +#### func (*SyncSlice) Get(index int) V +*** +#### func (*SyncSlice) GetWithRange(start int, end int) []V +*** +#### func (*SyncSlice) Set(index int, value V) +*** +#### func (*SyncSlice) Append(values ...V) +*** +#### func (*SyncSlice) Release() +*** +#### func (*SyncSlice) Clear() +*** +#### func (*SyncSlice) GetData() []V +*** diff --git a/utils/collection/mappings/README.md b/utils/collection/mappings/README.md new file mode 100644 index 0000000..0fa7236 --- /dev/null +++ b/utils/collection/mappings/README.md @@ -0,0 +1,47 @@ +# Mappings + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewSyncMap](#NewSyncMap)|创建一个 SyncMap + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[SyncMap](#syncmap)|是基于 sync.RWMutex 实现的线程安全的 map + +
+ + +*** +## 详情信息 +#### func NewSyncMap(source ...map[K]V) *SyncMap[K, V] + +> 创建一个 SyncMap + +*** +### SyncMap `STRUCT` +是基于 sync.RWMutex 实现的线程安全的 map + - 适用于要考虑并发读写但是并发读写的频率不高的情况 +```go +type SyncMap[K comparable, V any] struct { + lock sync.RWMutex + data map[K]V + atom bool +} +``` diff --git a/utils/combination/README.md b/utils/combination/README.md index e92938c..0efb527 100644 --- a/utils/combination/README.md +++ b/utils/combination/README.md @@ -1,25 +1,389 @@ # Combination -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/combination) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -`Combination` 包是一个实用的工具,提供了一系列用于生成和处理组合的函数,以帮助开发者处理复杂的组合问题。 +combination 包提供了一些实用的组合函数。 -## 特性 -### 泛型类型 +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 -在 `Minotaur` 中,我们的组合函数是泛型类型,这意味着它们可以处理任意类型的元素,为开发者提供了灵活性,使得这个包可以在各种不同的场景和应用中使用。 -### 算法描述 +> 包级函数定义 -我们的组合函数使用了特定的算法,以便快速地生成和处理组合。这些算法被设计为能够处理各种复杂的组合问题,以确保结果的准确性。 +|函数名称|描述 +|:--|:-- +|[NewCombination](#NewCombination)|创建一个新的组合器 +|[WithEvaluation](#WithEvaluation)|设置组合评估函数 +|[NewMatcher](#NewMatcher)|创建一个新的匹配器 +|[WithMatcherEvaluation](#WithMatcherEvaluation)|设置匹配器评估函数 +|[WithMatcherLeastLength](#WithMatcherLeastLength)|通过匹配最小长度的组合创建匹配器 +|[WithMatcherLength](#WithMatcherLength)|通过匹配长度的组合创建匹配器 +|[WithMatcherMostLength](#WithMatcherMostLength)|通过匹配最大长度的组合创建匹配器 +|[WithMatcherIntervalLength](#WithMatcherIntervalLength)|通过匹配长度区间的组合创建匹配器 +|[WithMatcherContinuity](#WithMatcherContinuity)|通过匹配连续的组合创建匹配器 +|[WithMatcherSame](#WithMatcherSame)|通过匹配相同的组合创建匹配器 +|[WithMatcherNCarryM](#WithMatcherNCarryM)|通过匹配 N 携带 M 的组合创建匹配器 +|[WithMatcherNCarryIndependentM](#WithMatcherNCarryIndependentM)|通过匹配 N 携带独立 M 的组合创建匹配器 +|[NewValidator](#NewValidator)|创建一个新的校验器 +|[WithValidatorHandle](#WithValidatorHandle)|通过特定的验证函数对组合进行验证 +|[WithValidatorHandleLength](#WithValidatorHandleLength)|校验组合的长度是否符合要求 +|[WithValidatorHandleLengthRange](#WithValidatorHandleLengthRange)|校验组合的长度是否在指定的范围内 +|[WithValidatorHandleLengthMin](#WithValidatorHandleLengthMin)|校验组合的长度是否大于等于指定的最小值 +|[WithValidatorHandleLengthMax](#WithValidatorHandleLengthMax)|校验组合的长度是否小于等于指定的最大值 +|[WithValidatorHandleLengthNot](#WithValidatorHandleLengthNot)|校验组合的长度是否不等于指定的值 +|[WithValidatorHandleTypeLength](#WithValidatorHandleTypeLength)|校验组合成员类型数量是否为指定的值 +|[WithValidatorHandleTypeLengthRange](#WithValidatorHandleTypeLengthRange)|校验组合成员类型数量是否在指定的范围内 +|[WithValidatorHandleTypeLengthMin](#WithValidatorHandleTypeLengthMin)|校验组合成员类型数量是否大于等于指定的最小值 +|[WithValidatorHandleTypeLengthMax](#WithValidatorHandleTypeLengthMax)|校验组合成员类型数量是否小于等于指定的最大值 +|[WithValidatorHandleTypeLengthNot](#WithValidatorHandleTypeLengthNot)|校验组合成员类型数量是否不等于指定的值 +|[WithValidatorHandleContinuous](#WithValidatorHandleContinuous)|校验组合成员是否连续 +|[WithValidatorHandleContinuousNot](#WithValidatorHandleContinuousNot)|校验组合成员是否不连续 +|[WithValidatorHandleGroupContinuous](#WithValidatorHandleGroupContinuous)|校验组合成员是否能够按类型分组并且连续 +|[WithValidatorHandleGroupContinuousN](#WithValidatorHandleGroupContinuousN)|校验组合成员是否能够按分组为 n 组类型并且连续 +|[WithValidatorHandleNCarryM](#WithValidatorHandleNCarryM)| 校验组合成员是否匹配 N 携带相同的 M 的组合 +|[WithValidatorHandleNCarryIndependentM](#WithValidatorHandleNCarryIndependentM)|校验组合成员是否匹配 N 携带独立的 M 的组合 -### 易于使用 -`Combination` 包的设计目标是使得开发者可以轻松地使用它。我们提供了清晰的文档和指南,帮助开发者理解和使用我们的组合函数。 +> 类型定义 -## 用途 +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Combination](#combination)|用于从多个匹配器内提取组合的数据结构 +|`STRUCT`|[Option](#option)|组合器选项 +|`INTERFACE`|[Item](#item)|暂无描述... +|`STRUCT`|[Matcher](#matcher)|用于从一组数据内提取组合的数据结构 +|`STRUCT`|[MatcherOption](#matcheroption)|匹配器选项 +|`STRUCT`|[Validator](#validator)|用于对组合进行验证的校验器 +|`STRUCT`|[ValidatorOption](#validatoroption)|暂无描述... -`Combination` 包可以在处理任何涉及到组合的问题时使用。无论是在数据分析、机器学习、算法设计,还是在其他需要处理组合的场景中,`Combination` 包都能够提供帮助。 +
-我们鼓励开发者探索 `Combination` 包的可能用途,并分享他们的经验和发现。 \ No newline at end of file + +*** +## 详情信息 +#### func NewCombination(options ...Option[T]) *Combination[T] + +> 创建一个新的组合器 + +*** +#### func WithEvaluation(evaluate func (items []T) float64) Option[T] + +> 设置组合评估函数 +> - 用于对组合进行评估,返回一个分值的评价函数 +> - 通过该选项将设置所有匹配器的默认评估函数为该函数 +> - 通过匹配器选项 WithMatcherEvaluation 可以覆盖该默认评估函数 +> - 默认的评估函数将返回一个随机数 + +*** +#### func NewMatcher(options ...MatcherOption[T]) *Matcher[T] + +> 创建一个新的匹配器 + +*** +#### func WithMatcherEvaluation(evaluate func (items []T) float64) MatcherOption[T] + +> 设置匹配器评估函数 +> - 用于对组合进行评估,返回一个分值的评价函数 +> - 通过该选项将覆盖匹配器的默认(WithEvaluation)评估函数 + +*** +#### func WithMatcherLeastLength(length int) MatcherOption[T] + +> 通过匹配最小长度的组合创建匹配器 +> - length: 组合的长度,表示需要匹配的组合最小数量 + +*** +#### func WithMatcherLength(length int) MatcherOption[T] + +> 通过匹配长度的组合创建匹配器 +> - length: 组合的长度,表示需要匹配的组合数量 + +*** +#### func WithMatcherMostLength(length int) MatcherOption[T] + +> 通过匹配最大长度的组合创建匹配器 +> - length: 组合的长度,表示需要匹配的组合最大数量 + +*** +#### func WithMatcherIntervalLength(min int, max int) MatcherOption[T] + +> 通过匹配长度区间的组合创建匹配器 +> - min: 组合的最小长度,表示需要匹配的组合最小数量 +> - max: 组合的最大长度,表示需要匹配的组合最大数量 + +*** +#### func WithMatcherContinuity(getIndex func (item T) Index) MatcherOption[T] + +> 通过匹配连续的组合创建匹配器 +> - index: 用于获取组合中元素的索引值,用于判断是否连续 + +*** +#### func WithMatcherSame(count int, getType func (item T) E) MatcherOption[T] + +> 通过匹配相同的组合创建匹配器 +> - count: 组合中相同元素的数量,当 count <= 0 时,表示相同元素的数量不限 +> - getType: 用于获取组合中元素的类型,用于判断是否相同 + +*** +#### func WithMatcherNCarryM(n int, m int, getType func (item T) E) MatcherOption[T] + +> 通过匹配 N 携带 M 的组合创建匹配器 +> - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 +> - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型需要全部相同 +> - getType: 用于获取组合中元素的类型,用于判断是否相同 + +*** +#### func WithMatcherNCarryIndependentM(n int, m int, getType func (item T) E) MatcherOption[T] + +> 通过匹配 N 携带独立 M 的组合创建匹配器 +> - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 +> - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型无需全部相同 +> - getType: 用于获取组合中元素的类型,用于判断是否相同 + +*** +#### func NewValidator(options ...ValidatorOption[T]) *Validator[T] + +> 创建一个新的校验器 + +*** +#### func WithValidatorHandle(handle func (items []T) bool) ValidatorOption[T] + +> 通过特定的验证函数对组合进行验证 + +*** +#### func WithValidatorHandleLength(length int) ValidatorOption[T] + +> 校验组合的长度是否符合要求 + +*** +#### func WithValidatorHandleLengthRange(min int, max int) ValidatorOption[T] + +> 校验组合的长度是否在指定的范围内 + +*** +#### func WithValidatorHandleLengthMin(min int) ValidatorOption[T] + +> 校验组合的长度是否大于等于指定的最小值 + +*** +#### func WithValidatorHandleLengthMax(max int) ValidatorOption[T] + +> 校验组合的长度是否小于等于指定的最大值 + +*** +#### func WithValidatorHandleLengthNot(length int) ValidatorOption[T] + +> 校验组合的长度是否不等于指定的值 + +*** +#### func WithValidatorHandleTypeLength(length int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员类型数量是否为指定的值 + +*** +#### func WithValidatorHandleTypeLengthRange(min int, max int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员类型数量是否在指定的范围内 + +*** +#### func WithValidatorHandleTypeLengthMin(min int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员类型数量是否大于等于指定的最小值 + +*** +#### func WithValidatorHandleTypeLengthMax(max int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员类型数量是否小于等于指定的最大值 + +*** +#### func WithValidatorHandleTypeLengthNot(length int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员类型数量是否不等于指定的值 + +*** +#### func WithValidatorHandleContinuous(getIndex func (item T) Index) ValidatorOption[T] + +> 校验组合成员是否连续 + +*** +#### func WithValidatorHandleContinuousNot(getIndex func (item T) Index) ValidatorOption[T] + +> 校验组合成员是否不连续 + +*** +#### func WithValidatorHandleGroupContinuous(getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] + +> 校验组合成员是否能够按类型分组并且连续 + +*** +#### func WithValidatorHandleGroupContinuousN(n int, getType func (item T) E, getIndex func (item T) Index) ValidatorOption[T] + +> 校验组合成员是否能够按分组为 n 组类型并且连续 + +*** +#### func WithValidatorHandleNCarryM(n int, m int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员是否匹配 N 携带相同的 M 的组合 +> - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 +> - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型需要全部相同 +> - getType: 用于获取组合中元素的类型,用于判断是否相同 + +*** +#### func WithValidatorHandleNCarryIndependentM(n int, m int, getType func (item T) E) ValidatorOption[T] + +> 校验组合成员是否匹配 N 携带独立的 M 的组合 +> - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同 +> - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型无需全部相同 +> - getType: 用于获取组合中元素的类型,用于判断是否相同 + +*** +### Combination `STRUCT` +用于从多个匹配器内提取组合的数据结构 +```go +type Combination[T Item] struct { + evaluate func([]T) float64 + matchers map[string]*Matcher[T] + priority []string +} +``` +#### func (*Combination) NewMatcher(name string, options ...MatcherOption[T]) *Combination[T] +> 添加一个新的匹配器 +*** +#### func (*Combination) AddMatcher(name string, matcher *Matcher[T]) *Combination[T] +> 添加一个匹配器 +*** +#### func (*Combination) RemoveMatcher(name string) *Combination[T] +> 移除一个匹配器 +*** +#### func (*Combination) Combinations(items []T) (result [][]T) +> 从一组数据中提取所有符合匹配器规则的组合 +*** +#### func (*Combination) CombinationsToName(items []T) (result map[string][][]T) +> 从一组数据中提取所有符合匹配器规则的组合,并返回匹配器名称 +*** +#### func (*Combination) Best(items []T) (name string, result []T) +> 从一组数据中提取符合匹配器规则的最佳组合 +
+查看 / 收起单元测试 + + +```go + +func TestCombination_Best(t *testing.T) { + combine := combination.NewCombination(combination.WithEvaluation(func(items []*Poker) float64 { + var total float64 + for _, item := range items { + total += float64(item.Point) + } + return total + })) + combine.NewMatcher("炸弹", combination.WithMatcherSame[*Poker, int](4, func(item *Poker) int { + return item.Point + })).NewMatcher("三带一", combination.WithMatcherNCarryM[*Poker, int](3, 1, func(item *Poker) int { + return item.Point + })) + var cards = []*Poker{{Point: 2, Color: 1}, {Point: 2, Color: 2}, {Point: 2, Color: 3}, {Point: 3, Color: 4}, {Point: 4, Color: 1}, {Point: 4, Color: 2}, {Point: 5, Color: 3}, {Point: 6, Color: 4}, {Point: 7, Color: 1}, {Point: 8, Color: 2}, {Point: 9, Color: 3}, {Point: 10, Color: 4}, {Point: 11, Color: 1}, {Point: 12, Color: 2}, {Point: 13, Color: 3}, {Point: 10, Color: 3}, {Point: 11, Color: 2}, {Point: 12, Color: 1}, {Point: 13, Color: 4}, {Point: 10, Color: 2}} + name, result := combine.Worst(cards) + fmt.Println("best:", name) + for _, item := range result { + fmt.Println(item) + } +} + +``` + + +
+ + +*** +#### func (*Combination) Worst(items []T) (name string, result []T) +> 从一组数据中提取符合匹配器规则的最差组合 +*** +### Option `STRUCT` +组合器选项 +```go +type Option[T Item] func(*Combination[T]) +``` +### Item `INTERFACE` + +```go +type Item interface{} +``` +### Matcher `STRUCT` +用于从一组数据内提取组合的数据结构 +```go +type Matcher[T Item] struct { + evaluate func(items []T) float64 + filter []func(items []T) [][]T +} +``` +#### func (*Matcher) AddFilter(filter func (items []T) [][]T) +> 添加一个筛选器 +> - 筛选器用于对组合进行筛选,返回一个二维数组,每个数组内的元素都是一个组合 +*** +#### func (*Matcher) Combinations(items []T) [][]T +> 从一组数据中提取所有符合筛选器规则的组合 +*** +#### func (*Matcher) Best(items []T) []T +> 从一组数据中提取符筛选器规则的最佳组合 +*** +#### func (*Matcher) Worst(items []T) []T +> 从一组数据中提取符筛选器规则的最差组合 +*** +### MatcherOption `STRUCT` +匹配器选项 +```go +type MatcherOption[T Item] func(matcher *Matcher[T]) +``` +### Validator `STRUCT` +用于对组合进行验证的校验器 +```go +type Validator[T Item] struct { + vh []func(items []T) bool +} +``` +#### func (*Validator) Validate(items []T) bool +> 校验组合是否符合要求 +
+查看 / 收起单元测试 + + +```go + +func TestValidator_Validate(t *testing.T) { + v := combination.NewValidator[*Card](combination.WithValidatorHandleContinuous[*Card, int](func(item *Card) int { + switch item.Point { + case "A": + return 1 + case "2", "3", "4", "5", "6", "7", "8", "9", "10": + return super.StringToInt(item.Point) + case "J": + return 11 + case "Q": + return 12 + case "K": + return 13 + } + return -1 + }), combination.WithValidatorHandleLength[*Card](3)) + cards := []*Card{{Point: "2", Color: "Spade"}, {Point: "4", Color: "Heart"}, {Point: "3", Color: "Diamond"}} + fmt.Println(v.Validate(cards)) +} + +``` + + +
+ + +*** +### ValidatorOption `STRUCT` + +```go +type ValidatorOption[T Item] func(validator *Validator[T]) +``` diff --git a/utils/compress/README.md b/utils/compress/README.md index 3fb8e3d..d576d74 100644 --- a/utils/compress/README.md +++ b/utils/compress/README.md @@ -1,5 +1,62 @@ # Compress -[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur/utils/compress) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) -该包提供了GZIP、ZIP和TAR的压缩和解压缩实现。开发者可以使用它来快速进行数据的压缩和解压缩。 \ No newline at end of file +compress 提供了一些用于压缩和解压缩数据的函数。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[GZipCompress](#GZipCompress)|对数据进行GZip压缩,返回bytes.Buffer和错误信息 +|[GZipUnCompress](#GZipUnCompress)|对已进行GZip压缩的数据进行解压缩,返回字节数组及错误信息 +|[TARCompress](#TARCompress)|对数据进行TAR压缩,返回bytes.Buffer和错误信息 +|[TARUnCompress](#TARUnCompress)|对已进行TAR压缩的数据进行解压缩,返回字节数组及错误信息 +|[ZIPCompress](#ZIPCompress)|对数据进行ZIP压缩,返回bytes.Buffer和错误信息 +|[ZIPUnCompress](#ZIPUnCompress)|对已进行ZIP压缩的数据进行解压缩,返回字节数组及错误信息 + + + +
+ + +*** +## 详情信息 +#### func GZipCompress(data []byte) bytes.Buffer, error + +> 对数据进行GZip压缩,返回bytes.Buffer和错误信息 + +*** +#### func GZipUnCompress(dataByte []byte) []byte, error + +> 对已进行GZip压缩的数据进行解压缩,返回字节数组及错误信息 + +*** +#### func TARCompress(data []byte) bytes.Buffer, error + +> 对数据进行TAR压缩,返回bytes.Buffer和错误信息 + +*** +#### func TARUnCompress(dataByte []byte) []byte, error + +> 对已进行TAR压缩的数据进行解压缩,返回字节数组及错误信息 + +*** +#### func ZIPCompress(data []byte) bytes.Buffer, error + +> 对数据进行ZIP压缩,返回bytes.Buffer和错误信息 + +*** +#### func ZIPUnCompress(dataByte []byte) []byte, error + +> 对已进行ZIP压缩的数据进行解压缩,返回字节数组及错误信息 + +*** diff --git a/utils/crypto/README.md b/utils/crypto/README.md new file mode 100644 index 0000000..88f6719 --- /dev/null +++ b/utils/crypto/README.md @@ -0,0 +1,86 @@ +# Crypto + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[EncryptBase64](#EncryptBase64)|对数据进行Base64编码 +|[DecodedBase64](#DecodedBase64)|对数据进行Base64解码 +|[EncryptCRC32](#EncryptCRC32)|对字符串进行CRC加密并返回其结果。 +|[DecodedCRC32](#DecodedCRC32)|对字节数组进行CRC加密并返回其结果。 +|[EncryptMD5](#EncryptMD5)|对字符串进行MD5加密并返回其结果。 +|[DecodedMD5](#DecodedMD5)|对字节数组进行MD5加密并返回其结果。 +|[EncryptSHA1](#EncryptSHA1)|对字符串进行SHA1加密并返回其结果。 +|[DecodedSHA1](#DecodedSHA1)|对字节数组进行SHA1加密并返回其结果。 +|[EncryptSHA256](#EncryptSHA256)|对字符串进行SHA256加密并返回其结果。 +|[DecodedSHA256](#DecodedSHA256)|对字节数组进行SHA256加密并返回其结果。 + + + +
+ + +*** +## 详情信息 +#### func EncryptBase64(data []byte) string + +> 对数据进行Base64编码 + +*** +#### func DecodedBase64(data string) []byte, error + +> 对数据进行Base64解码 + +*** +#### func EncryptCRC32(str string) uint32 + +> 对字符串进行CRC加密并返回其结果。 + +*** +#### func DecodedCRC32(data []byte) uint32 + +> 对字节数组进行CRC加密并返回其结果。 + +*** +#### func EncryptMD5(str string) string + +> 对字符串进行MD5加密并返回其结果。 + +*** +#### func DecodedMD5(data []byte) string + +> 对字节数组进行MD5加密并返回其结果。 + +*** +#### func EncryptSHA1(str string) string + +> 对字符串进行SHA1加密并返回其结果。 + +*** +#### func DecodedSHA1(data []byte) string + +> 对字节数组进行SHA1加密并返回其结果。 + +*** +#### func EncryptSHA256(str string) string + +> 对字符串进行SHA256加密并返回其结果。 + +*** +#### func DecodedSHA256(data []byte) string + +> 对字节数组进行SHA256加密并返回其结果。 + +*** diff --git a/utils/deck/README.md b/utils/deck/README.md new file mode 100644 index 0000000..f6f342d --- /dev/null +++ b/utils/deck/README.md @@ -0,0 +1,133 @@ +# Deck + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +deck 包中的内容用于针对一堆内容的管理,适用但不限于牌堆、麻将牌堆等情况。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewDeck](#NewDeck)|创建一个新的甲板 +|[NewGroup](#NewGroup)|创建一个新的组 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Deck](#deck)|甲板,用于针对一堆 Group 进行管理的数据结构 +|`STRUCT`|[Group](#group)|甲板中的组,用于针对一堆内容进行管理的数据结构 +|`INTERFACE`|[Item](#item)|甲板成员 + +
+ + +*** +## 详情信息 +#### func NewDeck() *Deck[I] + +> 创建一个新的甲板 + +*** +#### func NewGroup(guid int64, fillHandle func (guid int64) []I) *Group[I] + +> 创建一个新的组 + +*** +### Deck `STRUCT` +甲板,用于针对一堆 Group 进行管理的数据结构 +```go +type Deck[I Item] struct { + groups map[int64]*Group[I] + sort []int64 +} +``` +#### func (*Deck) AddGroup(group *Group[I]) +> 将一个组添加到甲板中 +*** +#### func (*Deck) RemoveGroup(guid int64) +> 移除甲板中的一个组 +*** +#### func (*Deck) GetCount() int +> 获取甲板中的组数量 +*** +#### func (*Deck) GetGroups() map[int64]*Group[I] +> 获取所有组 +*** +#### func (*Deck) GetGroupsSlice() []*Group[I] +> 获取所有组 +*** +#### func (*Deck) GetNext(guid int64) *Group[I] +> 获取特定组的下一个组 +*** +#### func (*Deck) GetPrev(guid int64) *Group[I] +> 获取特定组的上一个组 +*** +### Group `STRUCT` +甲板中的组,用于针对一堆内容进行管理的数据结构 +```go +type Group[I Item] struct { + guid int64 + fillHandle func(guid int64) []I + items []I +} +``` +#### func (*Group) GetGuid() int64 +> 获取组的 guid +*** +#### func (*Group) Fill() +> 将该组的数据填充为 WithGroupFillHandle 中设置的内容 +*** +#### func (*Group) Pop() (item I) +> 从顶部获取一个内容 +*** +#### func (*Group) PopN(n int) (items []I) +> 从顶部获取指定数量的内容 +*** +#### func (*Group) PressOut() (item I) +> 从底部压出一个内容 +*** +#### func (*Group) PressOutN(n int) (items []I) +> 从底部压出指定数量的内容 +*** +#### func (*Group) Push(item I) +> 向顶部压入一个内容 +*** +#### func (*Group) PushN(items []I) +> 向顶部压入指定数量的内容 +*** +#### func (*Group) Insert(item I) +> 向底部插入一个内容 +*** +#### func (*Group) InsertN(items []I) +> 向底部插入指定数量的内容 +*** +#### func (*Group) Pull(index int) (item I) +> 从特定位置拔出一个内容 +*** +#### func (*Group) Thrust(index int, item I) +> 向特定位置插入一个内容 +*** +#### func (*Group) IsFree() bool +> 检查组是否为空 +*** +#### func (*Group) GetCount() int +> 获取组中剩余的内容数量 +*** +#### func (*Group) GetItem(index int) I +> 获取组中的指定内容 +*** +### Item `INTERFACE` +甲板成员 +```go +type Item interface{} +``` diff --git a/utils/file/README.md b/utils/file/README.md new file mode 100644 index 0000000..aa2cf96 --- /dev/null +++ b/utils/file/README.md @@ -0,0 +1,103 @@ +# File + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[PathExist](#PathExist)|路径是否存在 +|[IsDir](#IsDir)|路径是否是文件夹 +|[WriterFile](#WriterFile)|向特定文件写入内容 +|[ReadOnce](#ReadOnce)|单次读取文件 +|[ReadBlockHook](#ReadBlockHook)|分块读取文件 +|[ReadLine](#ReadLine)|分行读取文件 +|[LineCount](#LineCount)|统计文件行数 +|[Paths](#Paths)|获取指定目录下的所有文件路径 +|[ReadLineWithParallel](#ReadLineWithParallel)|并行的分行读取文件并行处理,处理过程中会将每一行的内容传入 handlerFunc 中进行处理 +|[FindLineChunks](#FindLineChunks)|查找文件按照每行划分的分块,每个分块的大小将在 chunkSize 和分割后的分块距离行首及行尾的距离中范围内 +|[FindLineChunksByOffset](#FindLineChunksByOffset)|该函数与 FindLineChunks 类似,不同的是该函数可以指定 offset 从指定位置开始读取文件 + + + +
+ + +*** +## 详情信息 +#### func PathExist(path string) bool, error + +> 路径是否存在 + +*** +#### func IsDir(path string) bool, error + +> 路径是否是文件夹 + +*** +#### func WriterFile(filePath string, content []byte) error + +> 向特定文件写入内容 + +*** +#### func ReadOnce(filePath string) []byte, error + +> 单次读取文件 +> - 一次性对整个文件进行读取,小文件读取可以很方便的一次性将文件内容读取出来,而大文件读取会造成性能影响。 + +*** +#### func ReadBlockHook(filePath string, bufferSize int, hook func (data []byte)) error + +> 分块读取文件 +> - 将filePath路径对应的文件数据并将读到的每一部分传入hook函数中,当过程中如果产生错误则会返回error。 +> - 分块读取可以在读取速度和内存消耗之间有一个很好的平衡。 + +*** +#### func ReadLine(filePath string, hook func (line string)) error + +> 分行读取文件 +> - 将filePath路径对应的文件数据并将读到的每一行传入hook函数中,当过程中如果产生错误则会返回error。 + +*** +#### func LineCount(filePath string) int + +> 统计文件行数 + +*** +#### func Paths(dir string) []string + +> 获取指定目录下的所有文件路径 +> - 包括了子目录下的文件 +> - 不包含目录 + +*** +#### func ReadLineWithParallel(filename string, chunkSize int64, handlerFunc func ( string), start ...int64) (n int64, err error) + +> 并行的分行读取文件并行处理,处理过程中会将每一行的内容传入 handlerFunc 中进行处理 +> - 由于是并行处理,所以处理过程中的顺序是不确定的。 +> - 可通过 start 参数指定开始读取的位置,如果不指定则从文件开头开始读取。 + +*** +#### func FindLineChunks(file *os.File, chunkSize int64) [][2]int64 + +> 查找文件按照每行划分的分块,每个分块的大小将在 chunkSize 和分割后的分块距离行首及行尾的距离中范围内 +> - 使用该函数得到的分块是完整的行,不会出现行被分割的情况 +> - 当过程中发生错误将会发生 panic +> - 返回值的成员是一个长度为 2 的数组,第一个元素是分块的起始位置,第二个元素是分块的结束位置 + +*** +#### func FindLineChunksByOffset(file *os.File, offset int64, chunkSize int64) [][2]int64 + +> 该函数与 FindLineChunks 类似,不同的是该函数可以指定 offset 从指定位置开始读取文件 + +*** diff --git a/utils/fsm/README.md b/utils/fsm/README.md new file mode 100644 index 0000000..f19ad85 --- /dev/null +++ b/utils/fsm/README.md @@ -0,0 +1,91 @@ +# Fsm + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewFSM](#NewFSM)|创建一个新的状态机 +|[WithEnterBeforeEvent](#WithEnterBeforeEvent)|设置状态进入前的回调 +|[WithEnterAfterEvent](#WithEnterAfterEvent)|设置状态进入后的回调 +|[WithUpdateEvent](#WithUpdateEvent)|设置状态内刷新的回调 +|[WithExitBeforeEvent](#WithExitBeforeEvent)|设置状态退出前的回调 +|[WithExitAfterEvent](#WithExitAfterEvent)|设置状态退出后的回调 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[FSM](#fsm)|状态机 +|`STRUCT`|[Option](#option)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewFSM(data Data) *FSM[State, Data] + +> 创建一个新的状态机 + +*** +#### func WithEnterBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] + +> 设置状态进入前的回调 +> - 在首次设置状态时,状态机本身的当前状态为零值状态 + +*** +#### func WithEnterAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] + +> 设置状态进入后的回调 + +*** +#### func WithUpdateEvent(fn func (state *FSM[State, Data])) Option[State, Data] + +> 设置状态内刷新的回调 + +*** +#### func WithExitBeforeEvent(fn func (state *FSM[State, Data])) Option[State, Data] + +> 设置状态退出前的回调 +> - 该阶段状态机的状态为退出前的状态,而非新的状态 + +*** +#### func WithExitAfterEvent(fn func (state *FSM[State, Data])) Option[State, Data] + +> 设置状态退出后的回调 +> - 该阶段状态机的状态为新的状态,而非退出前的状态 + +*** +### FSM `STRUCT` +状态机 +```go +type FSM[State comparable, Data any] struct { + prev *State + current *State + data Data + states map[State]struct{} + enterBeforeEventHandles map[State][]func(state *FSM[State, Data]) + enterAfterEventHandles map[State][]func(state *FSM[State, Data]) + updateEventHandles map[State][]func(state *FSM[State, Data]) + exitBeforeEventHandles map[State][]func(state *FSM[State, Data]) + exitAfterEventHandles map[State][]func(state *FSM[State, Data]) +} +``` +### Option `STRUCT` + +```go +type Option[State comparable, Data any] func(fsm *FSM[State, Data], state State) +``` diff --git a/utils/generator/astgo/README.md b/utils/generator/astgo/README.md new file mode 100644 index 0000000..282a53c --- /dev/null +++ b/utils/generator/astgo/README.md @@ -0,0 +1,163 @@ +# Astgo + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewPackage](#NewPackage)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Comment](#comment)|暂无描述... +|`STRUCT`|[Field](#field)|暂无描述... +|`STRUCT`|[File](#file)|暂无描述... +|`STRUCT`|[Function](#function)|暂无描述... +|`STRUCT`|[Package](#package)|暂无描述... +|`STRUCT`|[Struct](#struct)|暂无描述... +|`STRUCT`|[Type](#type)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewPackage(dir string) *Package, error + + +
+查看 / 收起单元测试 + + +```go + +func TestNewPackage(t *testing.T) { + p, err := astgo.NewPackage(`/Users/kercylan/Coding.localized/Go/minotaur/server`) + if err != nil { + panic(err) + } + fmt.Println(string(super.MarshalIndentJSON(p, "", " "))) +} + +``` + + +
+ + +*** +### Comment `STRUCT` + +```go +type Comment struct { + Comments []string + Clear []string +} +``` +### Field `STRUCT` + +```go +type Field struct { + Anonymous bool + Name string + Type *Type + Comments *Comment +} +``` +### File `STRUCT` + +```go +type File struct { + af *ast.File + owner *Package + FilePath string + Structs []*Struct + Functions []*Function + Comment *Comment +} +``` +#### func (*File) Package() string +*** +### Function `STRUCT` + +```go +type Function struct { + decl *ast.FuncDecl + Name string + Internal bool + Generic []*Field + Params []*Field + Results []*Field + Comments *Comment + Struct *Field + IsExample bool + IsTest bool + IsBenchmark bool + Test bool +} +``` +#### func (*Function) Code() string +*** +### Package `STRUCT` + +```go +type Package struct { + Dir string + Name string + Dirs []string + Files []*File + Functions map[string]*Function +} +``` +#### func (*Package) StructFunc(name string) []*Function +*** +#### func (*Package) PackageFunc() []*Function +*** +#### func (*Package) Structs() []*Struct +*** +#### func (*Package) FileComments() *Comment +*** +#### func (*Package) GetUnitTest(f *Function) *Function +*** +#### func (*Package) GetExampleTest(f *Function) *Function +*** +#### func (*Package) GetBenchmarkTest(f *Function) *Function +*** +### Struct `STRUCT` + +```go +type Struct struct { + Name string + Internal bool + Interface bool + Comments *Comment + Generic []*Field + Fields []*Field + Type *Type + Test bool +} +``` +### Type `STRUCT` + +```go +type Type struct { + expr ast.Expr + Sign string + IsPointer bool + Name string +} +``` diff --git a/utils/generator/astgo/comment.go b/utils/generator/astgo/comment.go new file mode 100644 index 0000000..68e7767 --- /dev/null +++ b/utils/generator/astgo/comment.go @@ -0,0 +1,30 @@ +package astgo + +import ( + "go/ast" + "strings" +) + +func newComment(cg *ast.CommentGroup) *Comment { + c := &Comment{} + if cg == nil { + return c + } + for i, comment := range cg.List { + c.Comments = append(c.Comments, comment.Text) + cc := strings.TrimPrefix(strings.Replace(comment.Text, "// ", "//", 1), "//") + if i == 0 { + s := strings.SplitN(cc, " ", 2) + if len(s) == 2 { + cc = s[1] + } + } + c.Clear = append(c.Clear, cc) + } + return c +} + +type Comment struct { + Comments []string + Clear []string +} diff --git a/utils/generator/astgo/field.go b/utils/generator/astgo/field.go new file mode 100644 index 0000000..1c00789 --- /dev/null +++ b/utils/generator/astgo/field.go @@ -0,0 +1,37 @@ +package astgo + +import ( + "fmt" + "go/ast" +) + +func newField(field *ast.Field) []*Field { + if len(field.Names) == 0 { + return []*Field{{ + Anonymous: true, + Type: newType(field.Type), + Comments: newComment(field.Comment), + }} + } else { + var fs []*Field + for _, name := range field.Names { + if name.String() == "Format" { + fmt.Println() + } + fs = append(fs, &Field{ + Anonymous: false, + Name: name.String(), + Type: newType(field.Type), + Comments: newComment(field.Comment), + }) + } + return fs + } +} + +type Field struct { + Anonymous bool // 匿名字段 + Name string // 字段名称 + Type *Type // 字段类型 + Comments *Comment // 注释 +} diff --git a/utils/generator/astgo/file.go b/utils/generator/astgo/file.go new file mode 100644 index 0000000..233b4a1 --- /dev/null +++ b/utils/generator/astgo/file.go @@ -0,0 +1,50 @@ +package astgo + +import ( + "go/ast" + "go/parser" + "go/token" + "strings" +) + +func newFile(owner *Package, filePath string) (*File, error) { + af, err := parser.ParseFile(token.NewFileSet(), filePath, nil, parser.ParseComments) + if err != nil { + return nil, err + } + f := &File{ + af: af, + owner: owner, + FilePath: filePath, + Comment: newComment(af.Doc), + } + for _, decl := range af.Decls { + switch typ := decl.(type) { + case *ast.FuncDecl: + f.Functions = append(f.Functions, newFunction(typ)) + case *ast.GenDecl: + _, ok := typ.Specs[0].(*ast.TypeSpec) + if ok { + s := newStruct(typ) + f.Structs = append(f.Structs, s) + if strings.HasSuffix(filePath, "_test.go") { + s.Test = true + } + } + } + } + return f, nil +} + +type File struct { + af *ast.File // 抽象语法树文件 + owner *Package // 所属包 + FilePath string // 文件路径 + Structs []*Struct // 包含的结构体 + Functions []*Function // 包含的函数 + Comment *Comment // 文件注释 +} + +func (f *File) Package() string { + return f.af.Name.String() +} diff --git a/utils/generator/astgo/function.go b/utils/generator/astgo/function.go new file mode 100644 index 0000000..feb9799 --- /dev/null +++ b/utils/generator/astgo/function.go @@ -0,0 +1,64 @@ +package astgo + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + "strings" +) + +func newFunction(astFunc *ast.FuncDecl) *Function { + f := &Function{ + decl: astFunc, + Name: astFunc.Name.String(), + Comments: newComment(astFunc.Doc), + } + f.IsTest = strings.HasPrefix(f.Name, "Test") + f.IsBenchmark = strings.HasPrefix(f.Name, "Benchmark") + f.IsExample = strings.HasPrefix(f.Name, "Example") + f.Test = f.IsTest || f.IsBenchmark || f.IsExample + f.Internal = f.Name[0] >= 97 && f.Name[0] <= 122 + if astFunc.Type.TypeParams != nil { + for _, field := range astFunc.Type.TypeParams.List { + f.Generic = append(f.Generic, newField(field)...) + } + } + if astFunc.Type.Params != nil { + for _, field := range astFunc.Type.Params.List { + f.Params = append(f.Params, newField(field)...) + } + } + if astFunc.Type.Results != nil { + for _, field := range astFunc.Type.Results.List { + f.Results = append(f.Results, newField(field)...) + } + } + if astFunc.Recv != nil { + f.Struct = newField(astFunc.Recv.List[0])[0] + } + return f +} + +type Function struct { + decl *ast.FuncDecl + Name string // 函数名 + Internal bool // 内部函数 + Generic []*Field // 泛型定义 + Params []*Field // 参数字段 + Results []*Field // 返回值字段 + Comments *Comment // 注释 + Struct *Field // 结构体函数对应的结构体字段 + IsExample bool // 是否为测试用例 + IsTest bool // 是否为单元测试 + IsBenchmark bool // 是否为基准测试 + Test bool // 是否为测试函数 +} + +func (f *Function) Code() string { + var bb bytes.Buffer + if err := format.Node(&bb, token.NewFileSet(), f.decl); err != nil { + panic(err) + } + return bb.String() + "\n" +} diff --git a/utils/generator/astgo/name.go b/utils/generator/astgo/name.go new file mode 100644 index 0000000..67abeae --- /dev/null +++ b/utils/generator/astgo/name.go @@ -0,0 +1,40 @@ +package astgo + +import ( + "go/ast" + "strings" +) + +func newName(expr ast.Expr) string { + var str strings.Builder + switch e := expr.(type) { + //case *ast.KeyValueExpr: + //case *ast.ArrayType: + //case *ast.StructType: + //case *ast.FuncType: + //case *ast.InterfaceType: + //case *ast.MapType: + //case *ast.ChanType: + case *ast.Ident: + str.WriteString(e.Name) + case *ast.Ellipsis: + str.WriteString(newName(e.Elt)) + //case *ast.BasicLit: + //case *ast.FuncLit: + //case *ast.CompositeLit: + //case *ast.ParenExpr: + case *ast.SelectorExpr: + str.WriteString(newName(e.X)) + case *ast.IndexExpr: + str.WriteString(newName(e.X)) + case *ast.IndexListExpr: + case *ast.SliceExpr: + case *ast.TypeAssertExpr: + case *ast.CallExpr: + case *ast.StarExpr: + str.WriteString(newName(e.X)) + case *ast.UnaryExpr: + case *ast.BinaryExpr: + } + return str.String() +} diff --git a/utils/generator/astgo/package.go b/utils/generator/astgo/package.go new file mode 100644 index 0000000..681564d --- /dev/null +++ b/utils/generator/astgo/package.go @@ -0,0 +1,125 @@ +package astgo + +import ( + "errors" + "fmt" + "github.com/kercylan98/minotaur/utils/str" + "os" + "path/filepath" +) + +func NewPackage(dir string) (*Package, error) { + pkg := &Package{Dir: dir, Functions: map[string]*Function{}} + fs, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + for _, f := range fs { + path := filepath.Join(pkg.Dir, f.Name()) + if f.IsDir() { + pkg.Dirs = append(pkg.Dirs, path) + continue + } + if filepath.Ext(path) != ".go" { + continue + } + f, err := newFile(pkg, path) + if err != nil { + continue + } + pkg.Files = append(pkg.Files, f) + for _, function := range f.Functions { + function := function + key := function.Name + if function.Struct != nil { + key = fmt.Sprintf("%s.%s", function.Struct.Name, key) + } + pkg.Functions[key] = function + } + } + if len(pkg.Files) == 0 { + return nil, errors.New("not found go ext file") + } + pkg.Name = pkg.Files[0].Package() + + return pkg, nil +} + +type Package struct { + Dir string + Name string + Dirs []string + Files []*File + Functions map[string]*Function +} + +func (p *Package) StructFunc(name string) []*Function { + var fs []*Function + for _, file := range p.Files { + for _, function := range file.Functions { + if function.Struct == nil { + continue + } + if function.Struct.Type.Name == name { + fs = append(fs, function) + } + } + } + return fs +} + +func (p *Package) PackageFunc() []*Function { + var fs []*Function + for _, file := range p.Files { + for _, function := range file.Functions { + if function.Struct == nil { + fs = append(fs, function) + } + } + } + return fs +} + +func (p *Package) Structs() []*Struct { + var structs []*Struct + for _, file := range p.Files { + for _, s := range file.Structs { + structs = append(structs, s) + } + } + return structs +} + +func (p *Package) FileComments() *Comment { + var comment = newComment(nil) + for _, file := range p.Files { + for _, c := range file.Comment.Comments { + comment.Comments = append(comment.Comments, c) + } + for _, c := range file.Comment.Clear { + comment.Clear = append(comment.Clear, c) + } + } + return comment +} + +func (p *Package) GetUnitTest(f *Function) *Function { + if f.Struct == nil { + return p.Functions[fmt.Sprintf("Test%s", str.FirstUpper(f.Name))] + } + return p.Functions[fmt.Sprintf("Test%s_%s", f.Struct.Type.Name, f.Name)] +} + +func (p *Package) GetExampleTest(f *Function) *Function { + if f.Struct == nil { + return p.Functions[fmt.Sprintf("Example%s", str.FirstUpper(f.Name))] + } + return p.Functions[fmt.Sprintf("Example%s_%s", f.Struct.Type.Name, f.Name)] +} + +func (p *Package) GetBenchmarkTest(f *Function) *Function { + if f.Struct == nil { + return p.Functions[fmt.Sprintf("Benchmark%s", str.FirstUpper(f.Name))] + } + return p.Functions[fmt.Sprintf("Benchmark%s_%s", f.Struct.Type.Name, f.Name)] +} diff --git a/utils/generator/astgo/package_test.go b/utils/generator/astgo/package_test.go new file mode 100644 index 0000000..d775db1 --- /dev/null +++ b/utils/generator/astgo/package_test.go @@ -0,0 +1,16 @@ +package astgo_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generator/astgo" + "github.com/kercylan98/minotaur/utils/super" + "testing" +) + +func TestNewPackage(t *testing.T) { + p, err := astgo.NewPackage(`/Users/kercylan/Coding.localized/Go/minotaur/server`) + if err != nil { + panic(err) + } + fmt.Println(string(super.MarshalIndentJSON(p, "", " "))) +} diff --git a/utils/generator/astgo/struct.go b/utils/generator/astgo/struct.go new file mode 100644 index 0000000..263e9d1 --- /dev/null +++ b/utils/generator/astgo/struct.go @@ -0,0 +1,49 @@ +package astgo + +import ( + "go/ast" +) + +func newStruct(astGen *ast.GenDecl) *Struct { + astTypeSpec := astGen.Specs[0].(*ast.TypeSpec) + s := &Struct{ + Name: astTypeSpec.Name.String(), + Comments: newComment(astGen.Doc), + } + s.Internal = s.Name[0] >= 97 && s.Name[0] <= 122 + if astTypeSpec.TypeParams != nil { + for _, field := range astTypeSpec.TypeParams.List { + s.Generic = append(s.Generic, newField(field)...) + } + } + + switch ts := astTypeSpec.Type.(type) { + case *ast.StructType: + if ts.Fields != nil { + for _, field := range ts.Fields.List { + s.Fields = append(s.Fields, newField(field)...) + } + } + case *ast.InterfaceType: + s.Interface = true + if ts.Methods != nil { + for _, field := range ts.Methods.List { + s.Fields = append(s.Fields, newField(field)...) + } + } + default: + s.Type = newType(ts) + } + return s +} + +type Struct struct { + Name string // 结构体名称 + Internal bool // 内部结构体 + Interface bool // 接口定义 + Comments *Comment // 注释 + Generic []*Field // 泛型类型 + Fields []*Field // 结构体字段 + Type *Type // 和结构体字段二选一,处理类似 type a string 的情况 + Test bool // 是否是测试结构体 +} diff --git a/utils/generator/astgo/type.go b/utils/generator/astgo/type.go new file mode 100644 index 0000000..c30a7a4 --- /dev/null +++ b/utils/generator/astgo/type.go @@ -0,0 +1,139 @@ +package astgo + +import ( + "fmt" + "go/ast" + "strings" +) + +func newType(expr ast.Expr) *Type { + var typ = &Type{ + expr: expr, + Name: newName(expr), + } + var str strings.Builder + switch e := typ.expr.(type) { + case *ast.KeyValueExpr: + k, v := newType(e.Key), newType(e.Value) + str.WriteString(fmt.Sprintf("%s : %s", k.Sign, v.Sign)) + case *ast.ArrayType: + isSlice := e.Len == nil + str.WriteByte('[') + if !isSlice { + length := newType(e.Len) + str.WriteString(length.Sign) + } + str.WriteByte(']') + element := newType(e.Elt) + str.WriteString(element.Sign) + case *ast.StructType: + str.WriteString("struct {") + if e.Fields != nil && len(e.Fields.List) > 0 { + str.WriteString("\n") + for _, field := range e.Fields.List { + f := newField(field)[0] + str.WriteString(fmt.Sprintf("%s %s\n", f.Name, f.Type.Sign)) + } + } + str.WriteString("}") + case *ast.FuncType: + var handler = func(fls *ast.FieldList) string { + var s string + if fls != nil { + var brackets bool + var params []string + for _, field := range fls.List { + f := newField(field)[0] + if !f.Anonymous { + brackets = true + } + params = append(params, fmt.Sprintf("%s %s", f.Name, f.Type.Sign)) + } + s = strings.Join(params, ", ") + if brackets { + s = "(" + s + ")" + } + } + return s + } + str.WriteString(strings.TrimSpace(fmt.Sprintf("func %s %s", func() string { + f := handler(e.Params) + if len(strings.TrimSpace(f)) == 0 { + return "()" + } + if !strings.HasPrefix(f, "(") { + return "(" + f + ")" + } + return f + }(), func() string { + f := handler(e.Results) + if e.Results != nil && len(e.Results.List) >= 2 && !strings.HasSuffix(f, "(") { + return "(" + f + ")" + } + return f + }()))) + case *ast.InterfaceType: + str.WriteString("interface {") + if e.Methods != nil && len(e.Methods.List) > 0 { + str.WriteString("\n") + for _, field := range e.Methods.List { + f := newField(field)[0] + str.WriteString(fmt.Sprintf("%s\n", f.Type.Sign)) + } + } + str.WriteString("}") + case *ast.MapType: + k, v := newType(e.Key), newType(e.Value) + str.WriteString(fmt.Sprintf("map[%s]%s", k.Sign, v.Sign)) + case *ast.ChanType: + t := newType(e.Value) + str.WriteString(fmt.Sprintf("chan %s", t.Sign)) + case *ast.Ident: + str.WriteString(e.Name) + case *ast.Ellipsis: + element := newType(e.Elt) + str.WriteString(fmt.Sprintf("...%s", element.Sign)) + case *ast.BasicLit: + str.WriteString(e.Value) + //case *ast.FuncLit: + //case *ast.CompositeLit: + //case *ast.ParenExpr: + case *ast.SelectorExpr: + t := newType(e.X) + str.WriteString(fmt.Sprintf("%s.%s", t.Sign, e.Sel.Name)) + case *ast.IndexExpr: + t := newType(e.X) + generic := newType(e.Index) + str.WriteString(fmt.Sprintf("%s[%s]", t.Sign, generic.Sign)) + case *ast.IndexListExpr: + self := newType(e.X) + var genericStr []string + for _, index := range e.Indices { + g := newType(index) + genericStr = append(genericStr, g.Sign) + } + str.WriteString(fmt.Sprintf("%s[%s]", self.Sign, strings.Join(genericStr, ", "))) + case *ast.SliceExpr: + case *ast.TypeAssertExpr: + case *ast.CallExpr: + case *ast.StarExpr: + typ.IsPointer = true + t := newType(e.X) + str.WriteString(fmt.Sprintf("*%s", t.Sign)) + case *ast.UnaryExpr: + str.WriteString(fmt.Sprintf("%s%s", e.Op.String(), newType(e.X).Sign)) + case *ast.BinaryExpr: + str.WriteString(newType(e.X).Sign) + str.WriteString(fmt.Sprintf(" %s ", e.Op.String())) + str.WriteString(newType(e.Y).Sign) + } + typ.Sign = str.String() + return typ +} + +type Type struct { + expr ast.Expr + Sign string // 类型签名 + IsPointer bool // 指针类型 + Name string // 类型名称 +} diff --git a/utils/generator/genreadme/README.md b/utils/generator/genreadme/README.md new file mode 100644 index 0000000..e4851ac --- /dev/null +++ b/utils/generator/genreadme/README.md @@ -0,0 +1,78 @@ +# Genreadme + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[New](#New)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Builder](#builder)|暂无描述... + +
+ + +*** +## 详情信息 +#### func New(pkgDirPath string, output string) *Builder, error + + +*** +### Builder `STRUCT` + +```go +type Builder struct { + p *astgo.Package + b *strings.Builder + o string +} +``` +#### func (*Builder) Generate() error +
+查看 / 收起单元测试 + + +```go + +func TestBuilder_Generate(t *testing.T) { + filepath.Walk("/Users/kercylan/Coding.localized/Go/minotaur", func(path string, info fs.FileInfo, err error) error { + if !info.IsDir() { + return nil + } + if strings.Contains(strings.TrimPrefix(path, "/Users/kercylan/Coding.localized/Go/minotaur"), ".") { + return nil + } + b, err := New(path, filepath.Join(path, "README.md")) + if err != nil { + return nil + } + if err = b.Generate(); err != nil { + panic(err) + } + return nil + }) +} + +``` + + +
+ + +*** diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go new file mode 100644 index 0000000..06cf694 --- /dev/null +++ b/utils/generator/genreadme/builder.go @@ -0,0 +1,345 @@ +package genreadme + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/collection" + "github.com/kercylan98/minotaur/utils/file" + "github.com/kercylan98/minotaur/utils/generator/astgo" + "github.com/kercylan98/minotaur/utils/str" + "github.com/kercylan98/minotaur/utils/super" + "go/format" + "strings" + "sync" +) + +func New(pkgDirPath string, output string) (*Builder, error) { + p, err := astgo.NewPackage(pkgDirPath) + if err != nil { + return nil, err + } + b := &Builder{ + p: p, + b: new(strings.Builder), + o: output, + } + return b, nil +} + +type Builder struct { + p *astgo.Package + b *strings.Builder + o string +} + +func (b *Builder) Generate() error { + //fmt.Println(string(super.MarshalIndentJSON(b.p, "", " "))) + b.genHeader() + b.genMenus() + b.genStructs() + + return file.WriterFile(b.o, []byte(b.b.String())) +} + +func (b *Builder) genHeader() { + b.title(1, str.FirstUpper(b.p.Name)) + b.newLine() + b.newLine(fmt.Sprintf(`[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur)`)) + b.newLine(fmt.Sprintf(`![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat)`)) + if len(b.p.FileComments().Clear) != 0 { + b.newLine().newLine(b.p.FileComments().Clear...).newLine() + } else { + b.newLine().newLine("暂无介绍...").newLine() + } + b.newLine() +} + +func (b *Builder) genMenus() { + var genTitleOnce sync.Once + var genTitle = func() { + genTitleOnce.Do(func() { + b.title(2, "目录导航") + b.newLine("列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️") + b.detailsStart("展开 / 折叠目录导航") + }) + } + + packageFunction := b.p.PackageFunc() + var structList []*astgo.Struct + for _, f := range b.p.Files { + if strings.HasSuffix(f.FilePath, "_test.go") { + continue + } + for _, structInfo := range f.Structs { + if structInfo.Test || structInfo.Internal { + continue + } + structList = append(structList, structInfo) + } + } + + if len(packageFunction) > 0 { + var pfGenOnce sync.Once + var pfGen = func() { + pfGenOnce.Do(func() { + genTitle() + b.quote("包级函数定义").newLine() + b.tableCel("函数名称", "描述") + }) + } + for _, function := range packageFunction { + if function.Test || function.Internal { + continue + } + pfGen() + b.tableRow( + fmt.Sprintf("[%s](#%s)", function.Name, function.Name), + collection.FindFirstOrDefaultInSlice(function.Comments.Clear, "暂无描述..."), + ) + } + b.newLine().newLine() + } + + if len(structList) > 0 { + var structGenOnce sync.Once + var structGen = func() { + structGenOnce.Do(func() { + genTitle() + b.quote("类型定义").newLine() + b.tableCel("类型", "名称", "描述") + }) + } + for _, structInfo := range structList { + if structInfo.Test || structInfo.Internal { + continue + } + structGen() + b.tableRow( + super.If(structInfo.Interface, "`INTERFACE`", "`STRUCT`"), + fmt.Sprintf("[%s](#%s)", structInfo.Name, strings.ToLower(structInfo.Name)), + collection.FindFirstOrDefaultInSlice(structInfo.Comments.Clear, "暂无描述..."), + ) + } + } + b.detailsEnd() + b.newLine("***") +} + +func (b *Builder) genStructs() { + var titleOnce sync.Once + var titleBuild = func() { + titleOnce.Do(func() { + b.title(2, "详情信息") + }) + } + + var funcHandler = func(params []*astgo.Field) string { + var s string + var brackets bool + var paramsStr []string + for _, field := range params { + if !field.Anonymous { + brackets = true + } + paramsStr = append(paramsStr, fmt.Sprintf("%s %s", field.Name, field.Type.Sign)) + } + s = strings.Join(paramsStr, ", ") + if brackets { + s = "(" + s + ")" + } + return s + } + + for _, function := range b.p.PackageFunc() { + if function.Internal || function.Test { + continue + } + titleBuild() + b.title(4, strings.TrimSpace(fmt.Sprintf("func %s%s %s", + function.Name, + func() string { + f := funcHandler(function.Params) + if !strings.HasPrefix(f, "(") { + f = "(" + f + ")" + } + return f + }(), + funcHandler(function.Results), + ))) + b.newLine(fmt.Sprintf(``, function.Name)) + b.quote() + for _, comment := range function.Comments.Clear { + b.quote(comment) + } + b.newLine() + if example := b.p.GetExampleTest(function); example != nil { + b.newLine("示例代码:", "```go\n", example.Code(), "```\n") + } + if unitTest := b.p.GetUnitTest(function); unitTest != nil { + b.detailsStart("查看 / 收起单元测试") + b.newLine("```go\n", unitTest.Code(), "```\n") + b.detailsEnd() + } + if benchmarkTest := b.p.GetBenchmarkTest(function); benchmarkTest != nil { + b.detailsStart("查看 / 收起基准测试") + b.newLine("```go\n", benchmarkTest.Code(), "```\n") + b.detailsEnd() + } + b.newLine("***") + } + + for _, f := range b.p.Files { + for _, structInfo := range f.Structs { + if structInfo.Internal || structInfo.Test { + continue + } + titleBuild() + b.title(3, fmt.Sprintf("%s `%s`", structInfo.Name, super.If(structInfo.Interface, "INTERFACE", "STRUCT"))) + b.newLine(structInfo.Comments.Clear...) + b.newLine("```go") + structDefine := fmt.Sprintf("type %s %s", + func() string { + var sb strings.Builder + sb.WriteString(structInfo.Name) + if len(structInfo.Generic) > 0 { + sb.WriteString("[") + var gs []string + for _, field := range structInfo.Generic { + gs = append(gs, fmt.Sprintf("%s %s", field.Name, field.Type.Sign)) + } + sb.WriteString(strings.Join(gs, ", ")) + sb.WriteString("]") + } + return sb.String() + }(), + func() string { + var sb strings.Builder + if structInfo.Type == nil { + var head = "struct" + if structInfo.Interface { + head = "interface" + } + sb.WriteString(head + " {") + if len(structInfo.Fields) > 0 { + sb.WriteString("\n") + } + if structInfo.Interface { + for _, field := range structInfo.Fields { + sb.WriteString(fmt.Sprintf("\t%s %s\n", field.Name, strings.TrimPrefix(field.Type.Sign, "func"))) + } + } else { + for _, field := range structInfo.Fields { + sb.WriteString(fmt.Sprintf("\t%s %s\n", field.Name, field.Type.Sign)) + } + } + sb.WriteString("}") + } else { + sb.WriteString(structInfo.Type.Sign) + } + return sb.String() + }()) + sdb, err := format.Source([]byte(structDefine)) + if err != nil { + fmt.Println(structDefine) + panic(err) + } + b.newLine(string(sdb)) + b.newLine("```") + + for _, function := range b.p.StructFunc(structInfo.Name) { + if function.Internal || function.Test { + continue + } + b.title(4, strings.TrimSpace(fmt.Sprintf("func (%s%s) %s%s %s", + super.If(function.Struct.Type.IsPointer, "*", ""), + structInfo.Name, + function.Name, + func() string { + f := funcHandler(function.Params) + if !strings.HasPrefix(f, "(") { + f = "(" + f + ")" + } + return f + }(), + funcHandler(function.Results), + ))) + b.quote() + for _, comment := range function.Comments.Clear { + b.quote(comment) + } + if function.Name == "Write" { + fmt.Println() + } + if example := b.p.GetExampleTest(function); example != nil { + b.newLine("示例代码:", "```go\n", example.Code(), "```\n") + } + if unitTest := b.p.GetUnitTest(function); unitTest != nil { + b.detailsStart("查看 / 收起单元测试") + b.newLine("```go\n", unitTest.Code(), "```\n") + b.detailsEnd() + } + if benchmarkTest := b.p.GetBenchmarkTest(function); benchmarkTest != nil { + b.detailsStart("查看 / 收起基准测试") + b.newLine("```go\n", benchmarkTest.Code(), "```\n") + b.detailsEnd() + } + b.newLine("***") + } + } + } +} + +func (b *Builder) newLine(text ...string) *Builder { + if len(text) == 0 { + b.b.WriteString("\n") + return b + } + for _, s := range text { + b.b.WriteString(s + "\n") + } + return b +} + +func (b *Builder) title(lv int, str string) *Builder { + var l string + for i := 0; i < lv; i++ { + l += "#" + } + b.b.WriteString(l + " " + str) + b.b.WriteString("\n") + return b +} + +func (b *Builder) quote(str ...string) *Builder { + for _, s := range str { + b.b.WriteString("> " + s) + b.newLine() + } + return b +} + +func (b *Builder) tableCel(str ...string) *Builder { + var a string + var c string + for _, s := range str { + a += "|" + s + c += "|:--" + } + b.newLine(a, c) + return b +} + +func (b *Builder) tableRow(str ...string) *Builder { + var c string + for _, s := range str { + c += "|" + s + } + return b.newLine(c) +} + +func (b *Builder) detailsStart(title string) *Builder { + return b.newLine("
", ""+title+"").newLine().newLine() +} + +func (b *Builder) detailsEnd() *Builder { + return b.newLine().newLine("
").newLine().newLine() +} diff --git a/utils/generator/genreadme/builder_test.go b/utils/generator/genreadme/builder_test.go new file mode 100644 index 0000000..9a420ab --- /dev/null +++ b/utils/generator/genreadme/builder_test.go @@ -0,0 +1,39 @@ +package genreadme + +import ( + "io/fs" + "path/filepath" + "strings" + "testing" +) + +func TestBuilder_Generate(t *testing.T) { + //b, err := New(`/Users/kercylan/Coding.localized/Go/minotaur/utils/buffer`, `/Users/kercylan/Coding.localized/Go/minotaur/utils/buffer/README.md`) + //if err != nil { + // panic(err) + //} + //if err = b.Generate(); err != nil { + // panic(err) + //} + //return + filepath.Walk("/Users/kercylan/Coding.localized/Go/minotaur", func(path string, info fs.FileInfo, err error) error { + if !info.IsDir() { + return nil + } + if strings.Contains(strings.TrimPrefix(path, "/Users/kercylan/Coding.localized/Go/minotaur"), ".") { + return nil + } + b, err := New( + path, + filepath.Join(path, "README.md"), + ) + if err != nil { + return nil + } + if err = b.Generate(); err != nil { + panic(err) + } + return nil + }) + +} diff --git a/utils/generic/README.md b/utils/generic/README.md new file mode 100644 index 0000000..c57f517 --- /dev/null +++ b/utils/generic/README.md @@ -0,0 +1,172 @@ +# Generic + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +generic 目的在于提供一组基于泛型的用于处理通用功能的函数和数据结构。该包旨在简化通用功能的实现,并提供一致的接口和易于使用的功能。 +主要特性: + - 通用功能:generic 包支持处理各种通用功能,如数据结构操作、算法实现和常用工具等。您可以使用这些功能来解决各种通用问题,并提高代码的复用性和可维护性。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[IsNil](#IsNil)|检查指定的值是否为 nil +|[IsAllNil](#IsAllNil)|检查指定的值是否全部为 nil +|[IsHasNil](#IsHasNil)|检查指定的值是否存在 nil + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`INTERFACE`|[IdR](#idr)|暂无描述... +|`INTERFACE`|[IDR](#idr)|暂无描述... +|`INTERFACE`|[IdW](#idw)|暂无描述... +|`INTERFACE`|[IDW](#idw)|暂无描述... +|`INTERFACE`|[IdR2W](#idr2w)|暂无描述... +|`INTERFACE`|[IDR2W](#idr2w)|暂无描述... +|`INTERFACE`|[Ordered](#ordered)|可排序类型 +|`INTERFACE`|[Number](#number)|数字类型 +|`INTERFACE`|[SignedNumber](#signednumber)|有符号数字类型 +|`INTERFACE`|[Integer](#integer)|整数类型 +|`INTERFACE`|[Signed](#signed)|有符号整数类型 +|`INTERFACE`|[Unsigned](#unsigned)|无符号整数类型 +|`INTERFACE`|[UnsignedNumber](#unsignednumber)|无符号数字类型 +|`INTERFACE`|[Float](#float)|浮点类型 +|`INTERFACE`|[Basic](#basic)|基本类型 + +
+ + +*** +## 详情信息 +#### func IsNil(v V) bool + +> 检查指定的值是否为 nil + +*** +#### func IsAllNil(v ...V) bool + +> 检查指定的值是否全部为 nil + +*** +#### func IsHasNil(v ...V) bool + +> 检查指定的值是否存在 nil + +*** +### IdR `INTERFACE` + +```go +type IdR[ID comparable] interface { + GetId() ID +} +``` +### IDR `INTERFACE` + +```go +type IDR[ID comparable] interface { + GetID() ID +} +``` +### IdW `INTERFACE` + +```go +type IdW[ID comparable] interface { + SetId(id ID) +} +``` +### IDW `INTERFACE` + +```go +type IDW[ID comparable] interface { + SetID(id ID) +} +``` +### IdR2W `INTERFACE` + +```go +type IdR2W[ID comparable] interface { + IdR[ID] + IdW[ID] +} +``` +### IDR2W `INTERFACE` + +```go +type IDR2W[ID comparable] interface { + IDR[ID] + IDW[ID] +} +``` +### Ordered `INTERFACE` +可排序类型 +```go +type Ordered interface { + Integer | Float | ~string +} +``` +### Number `INTERFACE` +数字类型 +```go +type Number interface { + Integer | Float +} +``` +### SignedNumber `INTERFACE` +有符号数字类型 +```go +type SignedNumber interface { + Signed | Float +} +``` +### Integer `INTERFACE` +整数类型 +```go +type Integer interface { + Signed | Unsigned +} +``` +### Signed `INTERFACE` +有符号整数类型 +```go +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} +``` +### Unsigned `INTERFACE` +无符号整数类型 +```go +type Unsigned interface { + UnsignedNumber | ~uintptr +} +``` +### UnsignedNumber `INTERFACE` +无符号数字类型 +```go +type UnsignedNumber interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} +``` +### Float `INTERFACE` +浮点类型 +```go +type Float interface { + ~float32 | ~float64 +} +``` +### Basic `INTERFACE` +基本类型 +```go +type Basic interface { + Signed | Unsigned | Float | ~string | ~bool | ~byte +} +``` diff --git a/utils/geometry/README.md b/utils/geometry/README.md new file mode 100644 index 0000000..177eb01 --- /dev/null +++ b/utils/geometry/README.md @@ -0,0 +1,1146 @@ +# Geometry + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +geometry 旨在提供一组用于处理几何形状和计算几何属性的函数和数据结构。该包旨在简化几何计算的过程,并提供一致的接口和易于使用的功能。 +主要特性: + - 几何形状:"geometry"包支持处理各种几何形状,如点、线、多边形和圆等。您可以使用这些形状来表示和操作实际世界中的几何对象。 + - 几何计算:该包提供了一系列函数,用于执行常见的几何计算,如计算两点之间的距离、计算线段的长度、计算多边形的面积等。这些函数旨在提供高效和准确的计算结果。 + - 坐标转换:"geometry"包还提供了一些函数,用于在不同坐标系之间进行转换。您可以将点从笛卡尔坐标系转换为极坐标系,或者从二维坐标系转换为三维坐标系等。 + - 简化接口:该包的设计目标之一是提供简化的接口,使几何计算变得更加直观和易于使用。您可以轻松地创建和操作几何对象,而无需处理繁琐的底层细节。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewCircle](#NewCircle)|通过传入圆的半径和需要的点数量,生成一个圆 +|[CalcCircleCentroidDistance](#CalcCircleCentroidDistance)|计算两个圆质心距离 +|[GetOppositionDirection](#GetOppositionDirection)|获取特定方向的对立方向 +|[GetDirectionNextWithCoordinate](#GetDirectionNextWithCoordinate)|获取特定方向上的下一个坐标 +|[GetDirectionNextWithPoint](#GetDirectionNextWithPoint)|获取特定方向上的下一个坐标 +|[GetDirectionNextWithPos](#GetDirectionNextWithPos)|获取位置在特定宽度和特定方向上的下一个位置 +|[CalcDirection](#CalcDirection)|计算点2位于点1的方向 +|[CalcDistanceWithCoordinate](#CalcDistanceWithCoordinate)|计算两点之间的距离 +|[CalcDistanceWithPoint](#CalcDistanceWithPoint)|计算两点之间的距离 +|[CalcDistanceSquared](#CalcDistanceSquared)|计算两点之间的平方距离 +|[CalcAngle](#CalcAngle)|计算点2位于点1之间的角度 +|[CalcNewCoordinate](#CalcNewCoordinate)|根据给定的x、y坐标、角度和距离计算新的坐标 +|[CalcRadianWithAngle](#CalcRadianWithAngle)|根据角度 angle 计算弧度 +|[CalcAngleDifference](#CalcAngleDifference)|计算两个角度之间的最小角度差 +|[CalcRayIsIntersect](#CalcRayIsIntersect)|根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞 +|[NewLineSegment](#NewLineSegment)|创建一根线段 +|[NewLineSegmentCap](#NewLineSegmentCap)|创建一根包含数据的线段 +|[NewLineSegmentCapWithLine](#NewLineSegmentCapWithLine)|通过已有线段创建一根包含数据的线段 +|[ConvertLineSegmentGeneric](#ConvertLineSegmentGeneric)|转换线段的泛型类型为特定类型 +|[PointOnLineSegmentWithCoordinate](#PointOnLineSegmentWithCoordinate)|通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +|[PointOnLineSegmentWithPos](#PointOnLineSegmentWithPos)|通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +|[PointOnLineSegmentWithPoint](#PointOnLineSegmentWithPoint)|通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +|[PointOnLineSegmentWithCoordinateInBounds](#PointOnLineSegmentWithCoordinateInBounds)|通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +|[PointOnLineSegmentWithPosInBounds](#PointOnLineSegmentWithPosInBounds)|通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +|[PointOnLineSegmentWithPointInBounds](#PointOnLineSegmentWithPointInBounds)|通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +|[CalcLineSegmentIsCollinear](#CalcLineSegmentIsCollinear)|检查两条线段在一个误差内是否共线 +|[CalcLineSegmentIsOverlap](#CalcLineSegmentIsOverlap)|通过对点进行排序来检查两条共线线段是否重叠,返回重叠线段 +|[CalcLineSegmentIsIntersect](#CalcLineSegmentIsIntersect)|计算两条线段是否相交 +|[CalcLineSegmentSlope](#CalcLineSegmentSlope)|计算线段的斜率 +|[CalcLineSegmentIntercept](#CalcLineSegmentIntercept)|计算线段的截距 +|[NewPoint](#NewPoint)|创建一个由 x、y 坐标组成的点 +|[NewPointCap](#NewPointCap)|创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 +|[NewPointCapWithData](#NewPointCapWithData)|通过设置数据的方式创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 +|[NewPointCapWithPoint](#NewPointCapWithPoint)|通过设置数据的方式创建一个由已有坐标组成的点,这个点具有一个数据容量 +|[CoordinateToPoint](#CoordinateToPoint)|将坐标转换为x、y的坐标数组 +|[CoordinateToPos](#CoordinateToPos)|将坐标转换为二维数组的顺序位置坐标 +|[PointToCoordinate](#PointToCoordinate)|将坐标数组转换为x和y坐标 +|[PointToPos](#PointToPos)|将坐标转换为二维数组的顺序位置 +|[PosToCoordinate](#PosToCoordinate)|通过宽度将一个二维数组的顺序位置转换为xy坐标 +|[PosToPoint](#PosToPoint)|通过宽度将一个二维数组的顺序位置转换为x、y的坐标数组 +|[PosToCoordinateX](#PosToCoordinateX)|通过宽度将一个二维数组的顺序位置转换为X坐标 +|[PosToCoordinateY](#PosToCoordinateY)|通过宽度将一个二维数组的顺序位置转换为Y坐标 +|[PointCopy](#PointCopy)|复制一个坐标数组 +|[PointToPosWithMulti](#PointToPosWithMulti)|将一组坐标转换为二维数组的顺序位置 +|[PosToPointWithMulti](#PosToPointWithMulti)|将一组二维数组的顺序位置转换为一组数组坐标 +|[PosSameRow](#PosSameRow)|返回两个顺序位置在同一宽度是否位于同一行 +|[DoublePointToCoordinate](#DoublePointToCoordinate)|将两个位置转换为 x1, y1, x2, y2 的坐标进行返回 +|[CalcProjectionPoint](#CalcProjectionPoint)|计算一个点到一条线段的最近点(即投影点)的。这个函数接收一个点和一条线段作为输入,线段由两个端点组成。 +|[GetAdjacentTranslatePos](#GetAdjacentTranslatePos)|获取一个连续位置的矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 +|[GetAdjacentTranslateCoordinateXY](#GetAdjacentTranslateCoordinateXY)|获取一个基于 x、y 的二维矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 +|[GetAdjacentTranslateCoordinateYX](#GetAdjacentTranslateCoordinateYX)|获取一个基于 y、x 的二维矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 +|[GetAdjacentDiagonalsPos](#GetAdjacentDiagonalsPos)|获取一个连续位置的矩阵中,特定位置相邻的对角线最多四个方向的位置 +|[GetAdjacentDiagonalsCoordinateXY](#GetAdjacentDiagonalsCoordinateXY)|获取一个基于 x、y 的二维矩阵中,特定位置相邻的对角线最多四个方向的位置 +|[GetAdjacentDiagonalsCoordinateYX](#GetAdjacentDiagonalsCoordinateYX)|获取一个基于 tx 的二维矩阵中,特定位置相邻的对角线最多四个方向的位置 +|[GetAdjacentPos](#GetAdjacentPos)|获取一个连续位置的矩阵中,特定位置相邻的最多八个方向的位置 +|[GetAdjacentCoordinateXY](#GetAdjacentCoordinateXY)|获取一个基于 x、y 的二维矩阵中,特定位置相邻的最多八个方向的位置 +|[GetAdjacentCoordinateYX](#GetAdjacentCoordinateYX)|获取一个基于 yx 的二维矩阵中,特定位置相邻的最多八个方向的位置 +|[CoordinateMatrixToPosMatrix](#CoordinateMatrixToPosMatrix)|将二维矩阵转换为顺序的二维矩阵 +|[GetShapeCoverageAreaWithPoint](#GetShapeCoverageAreaWithPoint)|通过传入的一组坐标 points 计算一个图形覆盖的矩形范围 +|[GetShapeCoverageAreaWithPos](#GetShapeCoverageAreaWithPos)|通过传入的一组坐标 positions 计算一个图形覆盖的矩形范围 +|[CoverageAreaBoundless](#CoverageAreaBoundless)|将一个图形覆盖矩形范围设置为无边的 +|[GenerateShapeOnRectangle](#GenerateShapeOnRectangle)|生成一组二维坐标的形状 +|[GenerateShapeOnRectangleWithCoordinate](#GenerateShapeOnRectangleWithCoordinate)|生成一组二维坐标的形状 +|[GetExpressibleRectangleBySize](#GetExpressibleRectangleBySize)|获取一个宽高可表达的所有特定尺寸以上的矩形形状 +|[GetExpressibleRectangle](#GetExpressibleRectangle)|获取一个宽高可表达的所有矩形形状 +|[GetRectangleFullPointsByXY](#GetRectangleFullPointsByXY)|通过开始结束坐标获取一个矩形包含的所有点 +|[GetRectangleFullPoints](#GetRectangleFullPoints)|获取一个矩形填充满后包含的所有点 +|[GetRectangleFullPos](#GetRectangleFullPos)|获取一个矩形填充满后包含的所有位置 +|[CalcRectangleCentroid](#CalcRectangleCentroid)|计算矩形质心 +|[SetShapeStringHasBorder](#SetShapeStringHasBorder)|设置 Shape.String 是拥有边界的 +|[SetShapeStringNotHasBorder](#SetShapeStringNotHasBorder)|设置 Shape.String 是没有边界的 +|[NewShape](#NewShape)|通过多个点生成一个形状进行返回 +|[NewShapeWithString](#NewShapeWithString)|通过字符串将指定 rune 转换为点位置生成形状进行返回 +|[CalcBoundingRadius](#CalcBoundingRadius)|计算多边形转换为圆的半径 +|[CalcBoundingRadiusWithCentroid](#CalcBoundingRadiusWithCentroid)|计算多边形在特定质心下圆的半径 +|[CalcTriangleTwiceArea](#CalcTriangleTwiceArea)|计算由 a、b、c 三个点组成的三角形的面积的两倍 +|[IsPointOnEdge](#IsPointOnEdge)|检查点是否在 edges 的任意一条边上 +|[ProjectionPointToShape](#ProjectionPointToShape)|将一个点投影到一个多边形上,找到离该点最近的投影点,并返回投影点和距离 +|[WithShapeSearchRectangleLowerLimit](#WithShapeSearchRectangleLowerLimit)|通过矩形宽高下限的方式搜索 +|[WithShapeSearchRectangleUpperLimit](#WithShapeSearchRectangleUpperLimit)|通过矩形宽高上限的方式搜索 +|[WithShapeSearchRightAngle](#WithShapeSearchRightAngle)|通过直角的方式进行搜索 +|[WithShapeSearchOppositionDirection](#WithShapeSearchOppositionDirection)|通过限制对立方向的方式搜索 +|[WithShapeSearchDirectionCount](#WithShapeSearchDirectionCount)|通过限制方向数量的方式搜索 +|[WithShapeSearchDirectionCountLowerLimit](#WithShapeSearchDirectionCountLowerLimit)|通过限制特定方向数量下限的方式搜索 +|[WithShapeSearchDirectionCountUpperLimit](#WithShapeSearchDirectionCountUpperLimit)|通过限制特定方向数量上限的方式搜索 +|[WithShapeSearchDeduplication](#WithShapeSearchDeduplication)|通过去重的方式进行搜索 +|[WithShapeSearchPointCountLowerLimit](#WithShapeSearchPointCountLowerLimit)|通过限制图形构成的最小点数进行搜索 +|[WithShapeSearchPointCountUpperLimit](#WithShapeSearchPointCountUpperLimit)|通过限制图形构成的最大点数进行搜索 +|[WithShapeSearchAsc](#WithShapeSearchAsc)|通过升序的方式进行搜索 +|[WithShapeSearchDesc](#WithShapeSearchDesc)|通过降序的方式进行搜索 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Circle](#circle)|圆形 +|`STRUCT`|[FloorPlan](#floorplan)|平面图 +|`STRUCT`|[Direction](#direction)|方向 +|`STRUCT`|[LineSegment](#linesegment)|通过两个点表示一根线段 +|`STRUCT`|[LineSegmentCap](#linesegmentcap)|可以包含一份额外数据的线段 +|`STRUCT`|[Point](#point)|表示了一个由 x、y 坐标组成的点 +|`STRUCT`|[PointCap](#pointcap)|表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量 +|`STRUCT`|[Shape](#shape)|通过多个点表示了一个形状 +|`STRUCT`|[ShapeSearchOption](#shapesearchoption)|图形搜索可选项,用于 Shape.ShapeSearch 搜索支持 + +
+ + +*** +## 详情信息 +#### func NewCircle(radius V, points int) Circle[V] + +> 通过传入圆的半径和需要的点数量,生成一个圆 + +示例代码: +```go + +func ExampleNewCircle() { + fmt.Println(geometry.NewCircle[float64](7, 12)) +} + +``` + +*** +#### func CalcCircleCentroidDistance(circle1 Circle[V], circle2 Circle[V]) V + +> 计算两个圆质心距离 + +*** +#### func GetOppositionDirection(direction Direction) Direction + +> 获取特定方向的对立方向 + +*** +#### func GetDirectionNextWithCoordinate(direction Direction, x V, y V) (nx V, ny V) + +> 获取特定方向上的下一个坐标 + +*** +#### func GetDirectionNextWithPoint(direction Direction, point Point[V]) Point[V] + +> 获取特定方向上的下一个坐标 + +*** +#### func GetDirectionNextWithPos(direction Direction, width V, pos V) V + +> 获取位置在特定宽度和特定方向上的下一个位置 +> - 需要注意的是,在左右方向时,当下一个位置不在矩形区域内时,将会返回上一行的末位置或下一行的首位置 + +*** +#### func CalcDirection(x1 V, y1 V, x2 V, y2 V) Direction + +> 计算点2位于点1的方向 + +*** +#### func CalcDistanceWithCoordinate(x1 V, y1 V, x2 V, y2 V) V + +> 计算两点之间的距离 + +*** +#### func CalcDistanceWithPoint(point1 Point[V], point2 Point[V]) V + +> 计算两点之间的距离 + +*** +#### func CalcDistanceSquared(x1 V, y1 V, x2 V, y2 V) V + +> 计算两点之间的平方距离 +> - 这个函数的主要用途是在需要计算两点之间距离的情况下,但不需要得到实际的距离值,而只需要比较距离大小。因为平方根运算相对较为耗时,所以在只需要比较大小的情况下,通常会使用平方距离。 + +*** +#### func CalcAngle(x1 V, y1 V, x2 V, y2 V) V + +> 计算点2位于点1之间的角度 + +*** +#### func CalcNewCoordinate(x V, y V, angle V, distance V) (newX V, newY V) + +> 根据给定的x、y坐标、角度和距离计算新的坐标 + +*** +#### func CalcRadianWithAngle(angle V) V + +> 根据角度 angle 计算弧度 + +*** +#### func CalcAngleDifference(angleA V, angleB V) V + +> 计算两个角度之间的最小角度差 + +*** +#### func CalcRayIsIntersect(x V, y V, angle V, shape Shape[V]) bool + +> 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞 + +*** +#### func NewLineSegment(start Point[V], end Point[V]) LineSegment[V] + +> 创建一根线段 + +*** +#### func NewLineSegmentCap(start Point[V], end Point[V], data Data) LineSegmentCap[V, Data] + +> 创建一根包含数据的线段 + +*** +#### func NewLineSegmentCapWithLine(line LineSegment[V], data Data) LineSegmentCap[V, Data] + +> 通过已有线段创建一根包含数据的线段 + +*** +#### func ConvertLineSegmentGeneric(line LineSegment[V]) LineSegment[TO] + +> 转换线段的泛型类型为特定类型 + +*** +#### func PointOnLineSegmentWithCoordinate(x1 V, y1 V, x2 V, y2 V, x V, y V) bool + +> 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 + +*** +#### func PointOnLineSegmentWithPos(width V, pos1 V, pos2 V, pos V) bool + +> 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 + +*** +#### func PointOnLineSegmentWithPoint(point1 Point[V], point2 Point[V], point Point[V]) bool + +> 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 + +*** +#### func PointOnLineSegmentWithCoordinateInBounds(x1 V, y1 V, x2 V, y2 V, x V, y V) bool + +> 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +> - 与 PointOnLineSegmentWithCoordinate 不同的是, PointOnLineSegmentWithCoordinateInBounds 中会判断线段及点的位置是否正确 + +*** +#### func PointOnLineSegmentWithPosInBounds(width V, pos1 V, pos2 V, pos V) bool + +> 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +> - 与 PointOnLineSegmentWithPos 不同的是, PointOnLineSegmentWithPosInBounds 中会判断线段及点的位置是否正确 + +*** +#### func PointOnLineSegmentWithPointInBounds(point1 Point[V], point2 Point[V], point Point[V]) bool + +> 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 +> - 与 PointOnLineSegmentWithPoint 不同的是, PointOnLineSegmentWithPointInBounds 中会判断线段及点的位置是否正确 + +*** +#### func CalcLineSegmentIsCollinear(line1 LineSegment[V], line2 LineSegment[V], tolerance V) bool + +> 检查两条线段在一个误差内是否共线 +> - 共线是指两条线段在同一直线上,即它们的延长线可以重合 + +*** +#### func CalcLineSegmentIsOverlap(line1 LineSegment[V], line2 LineSegment[V]) (line LineSegment[V], overlap bool) + +> 通过对点进行排序来检查两条共线线段是否重叠,返回重叠线段 + +*** +#### func CalcLineSegmentIsIntersect(line1 LineSegment[V], line2 LineSegment[V]) bool + +> 计算两条线段是否相交 + +
+查看 / 收起单元测试 + + +```go + +func TestCalcLineSegmentIsIntersect(t *testing.T) { + line1 := geometry.NewLineSegment(geometry.NewPoint(1, 1), geometry.NewPoint(3, 5)) + line2 := geometry.NewLineSegment(geometry.NewPoint(0, 5), geometry.NewPoint(3, 6)) + fmt.Println(geometry.CalcLineSegmentIsIntersect(line1, line2)) +} + +``` + + +
+ + +*** +#### func CalcLineSegmentSlope(line LineSegment[V]) V + +> 计算线段的斜率 + +*** +#### func CalcLineSegmentIntercept(line LineSegment[V]) V + +> 计算线段的截距 + +*** +#### func NewPoint(x V, y V) Point[V] + +> 创建一个由 x、y 坐标组成的点 + +
+查看 / 收起单元测试 + + +```go + +func TestNewPoint(t *testing.T) { + p := [2]int{1, 1} + fmt.Println(PointToPos(9, p)) +} + +``` + + +
+ + +*** +#### func NewPointCap(x V, y V) PointCap[V, D] + +> 创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 + +*** +#### func NewPointCapWithData(x V, y V, data D) PointCap[V, D] + +> 通过设置数据的方式创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 + +*** +#### func NewPointCapWithPoint(point Point[V], data D) PointCap[V, D] + +> 通过设置数据的方式创建一个由已有坐标组成的点,这个点具有一个数据容量 + +*** +#### func CoordinateToPoint(x V, y V) Point[V] + +> 将坐标转换为x、y的坐标数组 + +*** +#### func CoordinateToPos(width V, x V, y V) V + +> 将坐标转换为二维数组的顺序位置坐标 +> - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 + +*** +#### func PointToCoordinate(position Point[V]) (x V, y V) + +> 将坐标数组转换为x和y坐标 + +*** +#### func PointToPos(width V, xy Point[V]) V + +> 将坐标转换为二维数组的顺序位置 +> - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 + +*** +#### func PosToCoordinate(width V, pos V) (x V, y V) + +> 通过宽度将一个二维数组的顺序位置转换为xy坐标 + +*** +#### func PosToPoint(width V, pos V) Point[V] + +> 通过宽度将一个二维数组的顺序位置转换为x、y的坐标数组 + +*** +#### func PosToCoordinateX(width V, pos V) V + +> 通过宽度将一个二维数组的顺序位置转换为X坐标 + +*** +#### func PosToCoordinateY(width V, pos V) V + +> 通过宽度将一个二维数组的顺序位置转换为Y坐标 + +*** +#### func PointCopy(point Point[V]) Point[V] + +> 复制一个坐标数组 + +*** +#### func PointToPosWithMulti(width V, points ...Point[V]) []V + +> 将一组坐标转换为二维数组的顺序位置 +> - 需要确保x的取值范围必须小于width,或者将会得到不正确的值 + +*** +#### func PosToPointWithMulti(width V, positions ...V) []Point[V] + +> 将一组二维数组的顺序位置转换为一组数组坐标 + +*** +#### func PosSameRow(width V, pos1 V, pos2 V) bool + +> 返回两个顺序位置在同一宽度是否位于同一行 + +*** +#### func DoublePointToCoordinate(point1 Point[V], point2 Point[V]) (x1 V, y1 V, x2 V, y2 V) + +> 将两个位置转换为 x1, y1, x2, y2 的坐标进行返回 + +*** +#### func CalcProjectionPoint(line LineSegment[V], point Point[V]) Point[V] + +> 计算一个点到一条线段的最近点(即投影点)的。这个函数接收一个点和一条线段作为输入,线段由两个端点组成。 +> - 该函数的主要用于需要计算一个点到一条线段的最近点的情况下 + +*** +#### func GetAdjacentTranslatePos(matrix []T, width P, pos P) (result []P) + +> 获取一个连续位置的矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 + +*** +#### func GetAdjacentTranslateCoordinateXY(matrix [][]T, x P, y P) (result []Point[P]) + +> 获取一个基于 x、y 的二维矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 + +*** +#### func GetAdjacentTranslateCoordinateYX(matrix [][]T, x P, y P) (result []Point[P]) + +> 获取一个基于 y、x 的二维矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置 + +*** +#### func GetAdjacentDiagonalsPos(matrix []T, width P, pos P) (result []P) + +> 获取一个连续位置的矩阵中,特定位置相邻的对角线最多四个方向的位置 + +*** +#### func GetAdjacentDiagonalsCoordinateXY(matrix [][]T, x P, y P) (result []Point[P]) + +> 获取一个基于 x、y 的二维矩阵中,特定位置相邻的对角线最多四个方向的位置 + +*** +#### func GetAdjacentDiagonalsCoordinateYX(matrix [][]T, x P, y P) (result []Point[P]) + +> 获取一个基于 tx 的二维矩阵中,特定位置相邻的对角线最多四个方向的位置 + +*** +#### func GetAdjacentPos(matrix []T, width P, pos P) (result []P) + +> 获取一个连续位置的矩阵中,特定位置相邻的最多八个方向的位置 + +*** +#### func GetAdjacentCoordinateXY(matrix [][]T, x P, y P) (result []Point[P]) + +> 获取一个基于 x、y 的二维矩阵中,特定位置相邻的最多八个方向的位置 + +*** +#### func GetAdjacentCoordinateYX(matrix [][]T, x P, y P) (result []Point[P]) + +> 获取一个基于 yx 的二维矩阵中,特定位置相邻的最多八个方向的位置 + +*** +#### func CoordinateMatrixToPosMatrix(matrix [][]V) (width int, posMatrix []V) + +> 将二维矩阵转换为顺序的二维矩阵 + +*** +#### func GetShapeCoverageAreaWithPoint(points ...Point[V]) (left V, right V, top V, bottom V) + +> 通过传入的一组坐标 points 计算一个图形覆盖的矩形范围 + +示例代码: +```go + +func ExampleGetShapeCoverageAreaWithPoint() { + var points []geometry.Point[int] + points = append(points, geometry.NewPoint(1, 1)) + points = append(points, geometry.NewPoint(2, 1)) + points = append(points, geometry.NewPoint(2, 2)) + left, right, top, bottom := geometry.GetShapeCoverageAreaWithPoint(points...) + fmt.Println(fmt.Sprintf("left: %v, right: %v, top: %v, bottom: %v", left, right, top, bottom)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestGetShapeCoverageAreaWithPoint(t *testing.T) { + Convey("TestGetShapeCoverageAreaWithPoint", t, func() { + var points []geometry.Point[int] + points = append(points, geometry.NewPoint(1, 1)) + points = append(points, geometry.NewPoint(2, 1)) + points = append(points, geometry.NewPoint(2, 2)) + left, right, top, bottom := geometry.GetShapeCoverageAreaWithPoint(points...) + So(left, ShouldEqual, 1) + So(right, ShouldEqual, 2) + So(top, ShouldEqual, 1) + So(bottom, ShouldEqual, 2) + }) +} + +``` + + +
+ + +*** +#### func GetShapeCoverageAreaWithPos(width V, positions ...V) (left V, right V, top V, bottom V) + +> 通过传入的一组坐标 positions 计算一个图形覆盖的矩形范围 + +示例代码: +```go + +func ExampleGetShapeCoverageAreaWithPos() { + left, right, top, bottom := geometry.GetShapeCoverageAreaWithPos(3, 4, 7, 8) + fmt.Println(fmt.Sprintf("left: %v, right: %v, top: %v, bottom: %v", left, right, top, bottom)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestGetShapeCoverageAreaWithPos(t *testing.T) { + Convey("TestGetShapeCoverageAreaWithPos", t, func() { + left, right, top, bottom := geometry.GetShapeCoverageAreaWithPos(3, 4, 7, 8) + So(left, ShouldEqual, 1) + So(right, ShouldEqual, 2) + So(top, ShouldEqual, 1) + So(bottom, ShouldEqual, 2) + }) +} + +``` + + +
+ + +*** +#### func CoverageAreaBoundless(l V, r V, t V, b V) (left V, right V, top V, bottom V) + +> 将一个图形覆盖矩形范围设置为无边的 +> - 无边化表示会将多余的部分进行裁剪,例如图形左边从 2 开始的时候,那么左边将会被裁剪到从 0 开始 + +示例代码: +```go + +func ExampleCoverageAreaBoundless() { + left, right, top, bottom := geometry.CoverageAreaBoundless(1, 2, 1, 2) + fmt.Println(fmt.Sprintf("left: %v, right: %v, top: %v, bottom: %v", left, right, top, bottom)) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestCoverageAreaBoundless(t *testing.T) { + Convey("TestCoverageAreaBoundless", t, func() { + left, right, top, bottom := geometry.CoverageAreaBoundless(1, 2, 1, 2) + So(left, ShouldEqual, 0) + So(right, ShouldEqual, 1) + So(top, ShouldEqual, 0) + So(bottom, ShouldEqual, 1) + }) +} + +``` + + +
+ + +*** +#### func GenerateShapeOnRectangle(points ...Point[V]) (result []PointCap[V, bool]) + +> 生成一组二维坐标的形状 +> - 这个形状将被在一个刚好能容纳形状的矩形中表示 +> - 为 true 的位置表示了形状的每一个点 + +
+查看 / 收起单元测试 + + +```go + +func TestGenerateShapeOnRectangle(t *testing.T) { + Convey("TestGenerateShapeOnRectangle", t, func() { + var points geometry.Shape[int] + points = append(points, geometry.NewPoint(1, 1)) + points = append(points, geometry.NewPoint(2, 1)) + points = append(points, geometry.NewPoint(2, 2)) + fmt.Println(points) + ps := geometry.GenerateShapeOnRectangle(points.Points()...) + So(ps[0].GetX(), ShouldEqual, 0) + So(ps[0].GetY(), ShouldEqual, 0) + So(ps[0].GetData(), ShouldEqual, true) + So(ps[1].GetX(), ShouldEqual, 1) + So(ps[1].GetY(), ShouldEqual, 0) + So(ps[1].GetData(), ShouldEqual, true) + So(ps[2].GetX(), ShouldEqual, 0) + So(ps[2].GetY(), ShouldEqual, 1) + So(ps[2].GetData(), ShouldEqual, false) + So(ps[3].GetX(), ShouldEqual, 1) + So(ps[3].GetY(), ShouldEqual, 1) + So(ps[3].GetData(), ShouldEqual, true) + }) +} + +``` + + +
+ + +*** +#### func GenerateShapeOnRectangleWithCoordinate(points ...Point[V]) (result [][]bool) + +> 生成一组二维坐标的形状 +> - 这个形状将被在一个刚好能容纳形状的矩形中表示 +> - 为 true 的位置表示了形状的每一个点 + +*** +#### func GetExpressibleRectangleBySize(width V, height V, minWidth V, minHeight V) (result []Point[V]) + +> 获取一个宽高可表达的所有特定尺寸以上的矩形形状 +> - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0) +> - 矩形尺寸由大到小 + +*** +#### func GetExpressibleRectangle(width V, height V) (result []Point[V]) + +> 获取一个宽高可表达的所有矩形形状 +> - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0) +> - 矩形尺寸由大到小 + +*** +#### func GetRectangleFullPointsByXY(startX V, startY V, endX V, endY V) (result []Point[V]) + +> 通过开始结束坐标获取一个矩形包含的所有点 +> - 例如 1,1 到 2,2 的矩形结果为 1,1 2,1 1,2 2,2 + +*** +#### func GetRectangleFullPoints(width V, height V) (result []Point[V]) + +> 获取一个矩形填充满后包含的所有点 + +*** +#### func GetRectangleFullPos(width V, height V) (result []V) + +> 获取一个矩形填充满后包含的所有位置 + +*** +#### func CalcRectangleCentroid(shape Shape[V]) Point[V] + +> 计算矩形质心 +> - 非多边形质心计算,仅为顶点的平均值 - 该区域中多边形因子的适当质心 + +*** +#### func SetShapeStringHasBorder() + +> 设置 Shape.String 是拥有边界的 + +*** +#### func SetShapeStringNotHasBorder() + +> 设置 Shape.String 是没有边界的 + +*** +#### func NewShape(points ...Point[V]) Shape[V] + +> 通过多个点生成一个形状进行返回 + +示例代码: +```go + +func ExampleNewShape() { + shape := geometry.NewShape[int](geometry.NewPoint(3, 0), geometry.NewPoint(3, 1), geometry.NewPoint(3, 2), geometry.NewPoint(3, 3), geometry.NewPoint(4, 3)) + fmt.Println(shape) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewShape(t *testing.T) { + Convey("TestNewShape", t, func() { + shape := geometry.NewShape[int](geometry.NewPoint(3, 0), geometry.NewPoint(3, 1), geometry.NewPoint(3, 2), geometry.NewPoint(3, 3), geometry.NewPoint(4, 3)) + fmt.Println(shape) + points := shape.Points() + count := shape.PointCount() + So(count, ShouldEqual, 5) + So(points[0], ShouldEqual, geometry.NewPoint(3, 0)) + So(points[1], ShouldEqual, geometry.NewPoint(3, 1)) + So(points[2], ShouldEqual, geometry.NewPoint(3, 2)) + So(points[3], ShouldEqual, geometry.NewPoint(3, 3)) + So(points[4], ShouldEqual, geometry.NewPoint(4, 3)) + }) +} + +``` + + +
+ + +*** +#### func NewShapeWithString(rows []string, point rune) (shape Shape[V]) + +> 通过字符串将指定 rune 转换为点位置生成形状进行返回 +> - 每个点的顺序从上到下,从左到右 + +示例代码: +```go + +func ExampleNewShapeWithString() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "###X###", "###X###", "###XX##"}, 'X') + fmt.Println(shape) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewShapeWithString(t *testing.T) { + Convey("TestNewShapeWithString", t, func() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "###X###", "###X###", "###XX##"}, 'X') + points := shape.Points() + count := shape.PointCount() + So(count, ShouldEqual, 5) + So(points[0], ShouldEqual, geometry.NewPoint(3, 0)) + So(points[1], ShouldEqual, geometry.NewPoint(3, 1)) + So(points[2], ShouldEqual, geometry.NewPoint(3, 2)) + So(points[3], ShouldEqual, geometry.NewPoint(3, 3)) + So(points[4], ShouldEqual, geometry.NewPoint(4, 3)) + }) +} + +``` + + +
+ + +*** +#### func CalcBoundingRadius(shape Shape[V]) V + +> 计算多边形转换为圆的半径 + +*** +#### func CalcBoundingRadiusWithCentroid(shape Shape[V], centroid Point[V]) V + +> 计算多边形在特定质心下圆的半径 + +*** +#### func CalcTriangleTwiceArea(a Point[V], b Point[V], c Point[V]) V + +> 计算由 a、b、c 三个点组成的三角形的面积的两倍 + +*** +#### func IsPointOnEdge(edges []LineSegment[V], point Point[V]) bool + +> 检查点是否在 edges 的任意一条边上 + +*** +#### func ProjectionPointToShape(point Point[V], shape Shape[V]) Point[V], V + +> 将一个点投影到一个多边形上,找到离该点最近的投影点,并返回投影点和距离 + +*** +#### func WithShapeSearchRectangleLowerLimit(minWidth int, minHeight int) ShapeSearchOption + +> 通过矩形宽高下限的方式搜索 + +*** +#### func WithShapeSearchRectangleUpperLimit(maxWidth int, maxHeight int) ShapeSearchOption + +> 通过矩形宽高上限的方式搜索 + +*** +#### func WithShapeSearchRightAngle() ShapeSearchOption + +> 通过直角的方式进行搜索 + +*** +#### func WithShapeSearchOppositionDirection(direction Direction) ShapeSearchOption + +> 通过限制对立方向的方式搜索 +> - 对立方向例如上不能与下共存 + +*** +#### func WithShapeSearchDirectionCount(count int) ShapeSearchOption + +> 通过限制方向数量的方式搜索 + +*** +#### func WithShapeSearchDirectionCountLowerLimit(direction Direction, count int) ShapeSearchOption + +> 通过限制特定方向数量下限的方式搜索 + +*** +#### func WithShapeSearchDirectionCountUpperLimit(direction Direction, count int) ShapeSearchOption + +> 通过限制特定方向数量上限的方式搜索 + +*** +#### func WithShapeSearchDeduplication() ShapeSearchOption + +> 通过去重的方式进行搜索 +> - 去重方式中每个点仅会被使用一次 + +*** +#### func WithShapeSearchPointCountLowerLimit(lowerLimit int) ShapeSearchOption + +> 通过限制图形构成的最小点数进行搜索 +> - 当搜索到的图形的点数量低于 lowerLimit 时,将被忽略 + +*** +#### func WithShapeSearchPointCountUpperLimit(upperLimit int) ShapeSearchOption + +> 通过限制图形构成的最大点数进行搜索 +> - 当搜索到的图形的点数量大于 upperLimit 时,将被忽略 + +*** +#### func WithShapeSearchAsc() ShapeSearchOption + +> 通过升序的方式进行搜索 + +*** +#### func WithShapeSearchDesc() ShapeSearchOption + +> 通过降序的方式进行搜索 + +*** +### Circle `STRUCT` +圆形 +```go +type Circle[V generic.SignedNumber] struct { + Shape[V] +} +``` +#### func (Circle) Radius() V +> 获取圆形半径 +*** +#### func (Circle) Centroid() Point[V] +> 获取圆形质心位置 +*** +#### func (Circle) Overlap(circle Circle[V]) bool +> 与另一个圆是否发生重叠 +*** +#### func (Circle) Area() V +> 获取圆形面积 +*** +#### func (Circle) Length() V +> 获取圆的周长 +*** +#### func (Circle) CentroidDistance(circle Circle[V]) V +> 计算与另一个圆的质心距离 +*** +### FloorPlan `STRUCT` +平面图 +```go +type FloorPlan []string +``` +#### func (FloorPlan) IsFree(point Point[int]) bool +> 检查位置是否为空格 +*** +#### func (FloorPlan) IsInBounds(point Point[int]) bool +> 检查位置是否在边界内 +*** +#### func (FloorPlan) Put(point Point[int], c rune) +> 设置平面图特定位置的字符 +*** +#### func (FloorPlan) String() string +> 获取平面图结果 +*** +### Direction `STRUCT` +方向 +```go +type Direction uint8 +``` +### LineSegment `STRUCT` +通过两个点表示一根线段 +```go +type LineSegment[V generic.SignedNumber] [2]Point[V] +``` +#### func (LineSegment) GetPoints() [2]Point[V] +> 获取该线段的两个点 +*** +#### func (LineSegment) GetStart() Point[V] +> 获取该线段的开始位置 +*** +#### func (LineSegment) GetEnd() Point[V] +> 获取该线段的结束位置 +*** +#### func (LineSegment) GetLength() V +> 获取该线段的长度 +*** +### LineSegmentCap `STRUCT` +可以包含一份额外数据的线段 +```go +type LineSegmentCap[V generic.SignedNumber, Data any] struct { + LineSegment[V] + Data Data +} +``` +### Point `STRUCT` +表示了一个由 x、y 坐标组成的点 +```go +type Point[V generic.SignedNumber] [2]V +``` +#### func (Point) GetX() V +> 返回该点的 x 坐标 +*** +#### func (Point) GetY() V +> 返回该点的 y 坐标 +*** +#### func (Point) GetXY() (x V, y V) +> 返回该点的 x、y 坐标 +*** +#### func (Point) GetPos(width V) V +> 返回该点位于特定宽度的二维数组的顺序位置 +*** +#### func (Point) GetOffset(x V, y V) Point[V] +> 获取偏移后的新坐标 +*** +#### func (Point) Negative() bool +> 返回该点是否是一个负数坐标 +*** +#### func (Point) OutOf(minWidth V, minHeight V, maxWidth V, maxHeight V) bool +> 返回该点在特定宽高下是否越界f +*** +#### func (Point) Equal(point Point[V]) bool +> 返回两个点是否相等 +*** +#### func (Point) Copy() Point[V] +> 复制一个点位置 +*** +#### func (Point) Add(point Point[V]) Point[V] +> 得到加上 point 后的点 +*** +#### func (Point) Sub(point Point[V]) Point[V] +> 得到减去 point 后的点 +*** +#### func (Point) Mul(point Point[V]) Point[V] +> 得到乘以 point 后的点 +*** +#### func (Point) Div(point Point[V]) Point[V] +> 得到除以 point 后的点 +*** +#### func (Point) Abs() Point[V] +> 返回位置的绝对值 +*** +#### func (Point) Max(point Point[V]) Point[V] +> 返回两个位置中每个维度的最大值组成的新的位置 +*** +#### func (Point) Min(point Point[V]) Point[V] +> 返回两个位置中每个维度的最小值组成的新的位置 +*** +### PointCap `STRUCT` +表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量 +```go +type PointCap[V generic.SignedNumber, D any] struct { + Point[V] + Data D +} +``` +### Shape `STRUCT` +通过多个点表示了一个形状 +```go +type Shape[V generic.SignedNumber] []Point[V] +``` +#### func (Shape) Points() []Point[V] +> 获取这个形状的所有点 +示例代码: +```go + +func ExampleShape_Points() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##"}, 'X') + points := shape.Points() + fmt.Println(points) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestShape_Points(t *testing.T) { + Convey("TestShape_Points", t, func() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##"}, 'X') + points := shape.Points() + So(points[0], ShouldEqual, geometry.NewPoint(3, 0)) + So(points[1], ShouldEqual, geometry.NewPoint(2, 1)) + So(points[2], ShouldEqual, geometry.NewPoint(3, 1)) + So(points[3], ShouldEqual, geometry.NewPoint(4, 1)) + }) +} + +``` + + +
+ + +*** +#### func (Shape) PointCount() int +> 获取这个形状的点数量 +示例代码: +```go + +func ExampleShape_PointCount() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##"}, 'X') + fmt.Println(shape.PointCount()) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestShape_PointCount(t *testing.T) { + Convey("TestShape_PointCount", t, func() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##"}, 'X') + So(shape.PointCount(), ShouldEqual, 4) + }) +} + +``` + + +
+ + +*** +#### func (Shape) Contains(point Point[V]) bool +> 返回该形状中是否包含点 +*** +#### func (Shape) ToCircle() Circle[V] +> 将形状转换为圆形进行处理 +> - 当形状非圆形时将会产生意外情况 +*** +#### func (Shape) String() string +> 将该形状转换为可视化的字符串进行返回 +示例代码: +```go + +func ExampleShape_String() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##"}, 'X') + fmt.Println(shape) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestShape_String(t *testing.T) { + Convey("TestShape_String", t, func() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##"}, 'X') + str := shape.String() + So(str, ShouldEqual, "[[3 0] [2 1] [3 1] [4 1]]\n# X #\nX X X") + }) +} + +``` + + +
+ + +*** +#### func (Shape) ShapeSearch(options ...ShapeSearchOption) (result []Shape[V]) +> 获取该形状中包含的所有图形组合及其位置 +> - 需要注意的是,即便图形最终表示为相同的,但是只要位置组合顺序不同,那么也将被认定为一种图形组合 +> - [[1 0] [1 1] [1 2]] 和 [[1 1] [1 0] [1 2]] 可以被视为两个图形组合 +> - 返回的坐标为原始形状的坐标 +> +> 可通过可选项对搜索结果进行过滤 +示例代码: +```go + +func ExampleShape_ShapeSearch() { + shape := geometry.NewShapeWithString[int]([]string{"###X###", "##XXX##", "###X###"}, 'X') + shapes := shape.ShapeSearch(geometry.WithShapeSearchDeduplication(), geometry.WithShapeSearchDesc()) + for _, shape := range shapes { + fmt.Println(shape) + } +} + +``` + +*** +#### func (Shape) Edges() (edges []LineSegment[V]) +> 获取该形状每一条边 +> - 该形状需要最少由3个点组成,否则将不会返回任意一边 +*** +#### func (Shape) IsPointOnEdge(point Point[V]) bool +> 检查点是否在该形状的一条边上 +*** +### ShapeSearchOption `STRUCT` +图形搜索可选项,用于 Shape.ShapeSearch 搜索支持 +```go +type ShapeSearchOption func(options *shapeSearchOptions) +``` diff --git a/utils/geometry/astar/README.md b/utils/geometry/astar/README.md new file mode 100644 index 0000000..100b4ad --- /dev/null +++ b/utils/geometry/astar/README.md @@ -0,0 +1,88 @@ +# Astar + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +astar 提供用于实现 A* 算法的函数和数据结构。A* 算法是一种常用的路径搜索算法,用于在图形或网络中找到最短路径。该包旨在简化 A* 算法的实现过程,并提供一致的接口和易于使用的功能。 +主要特性: + - 图形表示:astar 包支持使用图形或网络来表示路径搜索的环境。您可以定义节点和边,以构建图形,并在其中执行路径搜索。 + - A* 算法:该包提供了 A* 算法的实现,用于在图形中找到最短路径。A* 算法使用启发式函数来评估节点的优先级,并选择最有希望的节点进行扩展,以达到最短路径的目标。 + - 自定义启发式函数:您可以根据具体问题定义自己的启发式函数,以指导 A* 算法的搜索过程。启发式函数用于估计从当前节点到目标节点的代价,以帮助算法选择最佳路径。 + - 可定制性:astar 包提供了一些可定制的选项,以满足不同场景下的需求。您可以设置节点的代价、边的权重等参数,以调整算法的行为。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Find](#Find)|使用 A* 算法在导航网格上查找从起点到终点的最短路径,并返回路径上的节点序列。 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`INTERFACE`|[Graph](#graph)|适用于 A* 算法的图数据结构接口定义,表示导航网格,其中包含了节点和连接节点的边。 + +
+ + +*** +## 详情信息 +#### func Find(graph Graph[Node], start Node, end Node, cost func (a Node) V, heuristic func (a Node) V) []Node + +> 使用 A* 算法在导航网格上查找从起点到终点的最短路径,并返回路径上的节点序列。 +> +> 参数: +> - graph: 图对象,类型为 Graph[Node],表示导航网格。 +> - start: 起点节点,类型为 Node,表示路径的起点。 +> - end: 终点节点,类型为 Node,表示路径的终点。 +> - cost: 路径代价函数,类型为 func(a, b Node) V,用于计算两个节点之间的代价。 +> - heuristic: 启发函数,类型为 func(a, b Node) V,用于估计从当前节点到目标节点的启发式代价。 +> +> 返回值: +> - []Node: 节点序列,表示从起点到终点的最短路径。如果找不到路径,则返回空序列。 +> +> 注意事项: +> - graph 对象表示导航网格,其中包含了节点和连接节点的边。 +> - start 和 end 分别表示路径的起点和终点。 +> - cost 函数用于计算两个节点之间的代价,可以根据实际情况自定义实现。 +> - heuristic 函数用于估计从当前节点到目标节点的启发式代价,可以根据实际情况自定义实现。 +> - 函数使用了 A* 算法来搜索最短路径。 +> - 函数内部使用了堆数据结构来管理待处理的节点。 +> - 函数返回一个节点序列,表示从起点到终点的最短路径。如果找不到路径,则返回空序列。 + +示例代码: +```go + +func ExampleFind() { + graph := Graph{FloorPlan: geometry.FloorPlan{"===========", "X XX X X", "X X XX X", "X XX X", "X XXX X", "X XX X X", "X XX X X", "==========="}} + paths := astar.Find[geometry.Point[int], int](graph, geometry.NewPoint(1, 1), geometry.NewPoint(8, 6), func(a, b geometry.Point[int]) int { + return geometry.CalcDistanceWithCoordinate(geometry.DoublePointToCoordinate(a, b)) + }, func(a, b geometry.Point[int]) int { + return geometry.CalcDistanceWithCoordinate(geometry.DoublePointToCoordinate(a, b)) + }) + for _, path := range paths { + graph.Put(path, '.') + } + fmt.Println(graph) +} + +``` + +*** +### Graph `INTERFACE` +适用于 A* 算法的图数据结构接口定义,表示导航网格,其中包含了节点和连接节点的边。 +```go +type Graph[Node comparable] interface { + Neighbours(node Node) []Node +} +``` +#### func (Graph) Neighbours(point geometry.Point[int]) []geometry.Point[int] +*** diff --git a/utils/geometry/dp/README.md b/utils/geometry/dp/README.md new file mode 100644 index 0000000..5be6dee --- /dev/null +++ b/utils/geometry/dp/README.md @@ -0,0 +1,114 @@ +# Dp + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +dp (DistributionPattern) 提供用于在二维数组中根据不同的特征标记为数组成员建立分布链接的函数和数据结构。该包的目标是实现快速查找与给定位置成员具有相同特征且位置紧邻的其他成员。 +主要特性: + - 分布链接机制:dp 包提供了一种分布链接的机制,可以根据成员的特征将它们链接在一起。这样,可以快速查找与给定成员具有相同特征且位置紧邻的其他成员。 + - 二维数组支持:该包支持在二维数组中建立分布链接。可以将二维数组中的成员视为节点,并根据其特征进行链接。 + - 快速查找功能:使用 dp 包提供的函数,可以快速查找与给定位置成员具有相同特征且位置紧邻的其他成员。这有助于在二维数组中进行相关性分析或查找相邻成员。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewDistributionPattern](#NewDistributionPattern)|构建一个分布图实例 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[DistributionPattern](#distributionpattern)|分布图 +|`STRUCT`|[Link](#link)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewDistributionPattern(sameKindVerifyHandle func (itemA Item) bool) *DistributionPattern[Item] + +> 构建一个分布图实例 + +
+查看 / 收起单元测试 + + +```go + +func TestNewDistributionPattern(t *testing.T) { + dp := NewDistributionPattern[int](func(itemA, itemB int) bool { + return itemA == itemB + }) + matrix := []int{1, 1, 2, 2, 2, 2, 1, 2, 2} + dp.LoadMatrixWithPos(3, matrix) + for pos, link := range dp.links { + fmt.Println(pos, link, fmt.Sprintf("%p", link)) + } + fmt.Println() + matrix[6] = 2 + dp.Refresh(6) + for pos, link := range dp.links { + fmt.Println(pos, link, fmt.Sprintf("%p", link)) + } +} + +``` + + +
+ + +*** +### DistributionPattern `STRUCT` +分布图 +```go +type DistributionPattern[Item any] struct { + matrix []Item + links map[int]map[int]Item + sameKindVerifyHandle func(itemA Item) bool + width int + usePos bool +} +``` +#### func (*DistributionPattern) GetLinks(pos int) (result []Link[Item]) +> 获取关联的成员 +> - 其中包含传入的 pos 成员 +*** +#### func (*DistributionPattern) HasLink(pos int) bool +> 检查一个位置是否包含除它本身外的其他关联成员 +*** +#### func (*DistributionPattern) LoadMatrix(matrix [][]Item) +> 通过二维矩阵加载分布图 +> - 通过该函数加载的分布图使用的矩阵是复制后的矩阵,因此无法直接通过刷新(Refresh)来更新分布关系 +> - 需要通过直接刷新的方式请使用 LoadMatrixWithPos +*** +#### func (*DistributionPattern) LoadMatrixWithPos(width int, matrix []Item) +> 通过二维矩阵加载分布图 +*** +#### func (*DistributionPattern) Refresh(pos int) +> 刷新特定位置的分布关系 +> - 由于 LoadMatrix 的矩阵是复制后的矩阵,所以任何外部的改动都不会影响到分布图的变化,在这种情况下,刷新将没有任何意义 +> - 需要通过直接刷新的方式请使用 LoadMatrixWithPos 加载矩阵,或者通过 RefreshWithItem 函数进行刷新 +*** +#### func (*DistributionPattern) RefreshWithItem(pos int, item Item) +> 通过特定的成员刷新特定位置的分布关系 +> - 如果矩阵通过 LoadMatrixWithPos 加载,将会重定向至 Refresh +*** +### Link `STRUCT` + +```go +type Link[V any] struct { + Pos int + Item V +} +``` diff --git a/utils/geometry/matrix/README.md b/utils/geometry/matrix/README.md new file mode 100644 index 0000000..0981cdf --- /dev/null +++ b/utils/geometry/matrix/README.md @@ -0,0 +1,97 @@ +# Matrix + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +matrix 提供了一个简单的二维数组的实现 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewMatrix](#NewMatrix)|生成特定宽高的二维矩阵 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Matrix](#matrix)|二维矩阵 + +
+ + +*** +## 详情信息 +#### func NewMatrix(width int, height int) *Matrix[T] + +> 生成特定宽高的二维矩阵 +> - 虽然提供了通过x、y坐标的操作函数,但是建议无论如何使用pos进行处理 +> - 该矩阵为XY,而非YX + +*** +### Matrix `STRUCT` +二维矩阵 +```go +type Matrix[T any] struct { + w int + h int + m []T +} +``` +#### func (*Matrix) GetWidth() int +> 获取二维矩阵宽度 +*** +#### func (*Matrix) GetHeight() int +> 获取二维矩阵高度 +*** +#### func (*Matrix) GetWidth2Height() (width int, height int) +> 获取二维矩阵的宽度和高度 +*** +#### func (*Matrix) GetMatrix() [][]T +> 获取二维矩阵 +> - 通常建议使用 GetMatrixWithPos 进行处理这样将拥有更高的效率 +*** +#### func (*Matrix) GetMatrixWithPos() []T +> 获取顺序的矩阵 +*** +#### func (*Matrix) Get(x int, y int) (value T) +> 获取特定坐标的内容 +*** +#### func (*Matrix) GetExist(x int, y int) (value T, exist bool) +> 获取特定坐标的内容,如果不存在则返回 false +*** +#### func (*Matrix) GetWithPos(pos int) (value T) +> 获取特定坐标的内容 +*** +#### func (*Matrix) Set(x int, y int, data T) +> 设置特定坐标的内容 +*** +#### func (*Matrix) SetWithPos(pos int, data T) +> 设置特定坐标的内容 +*** +#### func (*Matrix) Swap(x1 int, y1 int, x2 int, y2 int) +> 交换两个位置的内容 +*** +#### func (*Matrix) SwapWithPos(pos1 int, pos2 int) +> 交换两个位置的内容 +*** +#### func (*Matrix) TrySwap(x1 int, y1 int, x2 int, y2 int, expressionHandle func (matrix *Matrix[T]) bool) +> 尝试交换两个位置的内容,交换后不满足表达式时进行撤销 +*** +#### func (*Matrix) TrySwapWithPos(pos1 int, pos2 int, expressionHandle func (matrix *Matrix[T]) bool) +> 尝试交换两个位置的内容,交换后不满足表达式时进行撤销 +*** +#### func (*Matrix) FillFull(generateHandle func (x int) T) +> 根据提供的生成器填充整个矩阵 +*** +#### func (*Matrix) FillFullWithPos(generateHandle func (pos int) T) +> 根据提供的生成器填充整个矩阵 +*** diff --git a/utils/geometry/navmesh/README.md b/utils/geometry/navmesh/README.md new file mode 100644 index 0000000..0374f76 --- /dev/null +++ b/utils/geometry/navmesh/README.md @@ -0,0 +1,129 @@ +# Navmesh + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +navmesh 提供了用于导航网格处理的函数和数据结构。导航网格是一种常用的数据结构,用于在游戏开发和虚拟环境中进行路径规划和导航。该包旨在简化导航网格的创建、查询和操作过程,并提供高效的导航功能。 +主要特性: + - 导航网格表示:navmesh 包支持使用导航网格来表示虚拟环境中的可行走区域和障碍物。您可以定义多边形区域和连接关系,以构建导航网格,并在其中执行路径规划和导航。 + - 导航算法:采用了 A* 算法作为导航算法,用于在导航网格中找到最短路径或最优路径。这些算法使用启发式函数和代价评估来指导路径搜索,并提供高效的路径规划能力。 + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewNavMesh](#NewNavMesh)|创建一个新的导航网格,并返回一个指向该导航网格的指针。 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[NavMesh](#navmesh)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewNavMesh(shapes []geometry.Shape[V], meshShrinkAmount V) *NavMesh[V] + +> 创建一个新的导航网格,并返回一个指向该导航网格的指针。 +> +> 参数: +> - shapes: 形状切片,类型为 []geometry.Shape[V],表示导航网格中的形状。 +> - meshShrinkAmount: 网格缩小量,类型为 V,表示导航网格的缩小量。 +> +> 返回值: +> - *NavMesh[V]: 指向创建的导航网格的指针。 +> +> 注意事项: +> - 导航网格的形状可以是任何几何形状。 +> - meshShrinkAmount 表示导航网格的缩小量,用于在形状之间创建链接时考虑形状的缩小效果。 +> - 函数内部使用了泛型类型参数 V,可以根据需要指定形状的坐标类型。 +> - 函数返回一个指向创建的导航网格的指针。 +> +> 使用建议: +> - 确保 NavMesh 计算精度的情况下,V 建议使用 float64 类型 + +*** +### NavMesh `STRUCT` + +```go +type NavMesh[V generic.SignedNumber] struct { + meshShapes []*shape[V] + meshShrinkAmount V +} +``` +#### func (*NavMesh) Neighbours(node *shape[V]) []*shape[V] +> 实现 astar.Graph 的接口,用于向 A* 算法提供相邻图形 +*** +#### func (*NavMesh) Find(point geometry.Point[V], maxDistance V) (distance V, findPoint geometry.Point[V], findShape geometry.Shape[V]) +> 用于在 NavMesh 中查找离给定点最近的形状,并返回距离、找到的点和找到的形状。 +> +> 参数: +> - point: 给定的点,类型为 geometry.Point[V],表示一个 V 维度的点坐标。 +> - maxDistance: 最大距离,类型为 V,表示查找的最大距离限制。 +> +> 返回值: +> - distance: 距离,类型为 V,表示离给定点最近的形状的距离。 +> - findPoint: 找到的点,类型为 geometry.Point[V],表示离给定点最近的点坐标。 +> - findShape: 找到的形状,类型为 geometry.Shape[V],表示离给定点最近的形状。 +> +> 注意事项: +> - 如果给定点在 NavMesh 中的某个形状内部或者在形状的边上,距离为 0,找到的形状为该形状,找到的点为给定点。 +> - 如果给定点不在任何形状内部或者形状的边上,将计算给定点到每个形状的距离,并找到最近的形状和对应的点。 +> - 距离的计算采用几何学中的投影点到形状的距离。 +> - 函数返回离给定点最近的形状的距离、找到的点和找到的形状。 +*** +#### func (*NavMesh) FindPath(start geometry.Point[V], end geometry.Point[V]) (result []geometry.Point[V]) +> 函数用于在 NavMesh 中查找从起点到终点的路径,并返回路径上的点序列。 +> +> 参数: +> - start: 起点,类型为 geometry.Point[V],表示路径的起始点。 +> - end: 终点,类型为 geometry.Point[V],表示路径的终点。 +> +> 返回值: +> - result: 路径上的点序列,类型为 []geometry.Point[V]。 +> +> 注意事项: +> - 函数首先根据起点和终点的位置,找到离它们最近的形状作为起点形状和终点形状。 +> - 如果起点或终点不在任何形状内部,且 NavMesh 的 meshShrinkAmount 大于0,则会考虑缩小的形状。 +> - 使用 A* 算法在 NavMesh 上搜索从起点形状到终点形状的最短路径。 +> - 使用漏斗算法对路径进行优化,以得到最终的路径点序列。 +示例代码: +```go + +func ExampleNavMesh_FindPath() { + fp := geometry.FloorPlan{"=================================", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "================================="} + var walkable []geometry.Shape[int] + walkable = append(walkable, geometry.NewShape(geometry.NewPoint(5, 5), geometry.NewPoint(15, 5), geometry.NewPoint(15, 15), geometry.NewPoint(5, 15)), geometry.NewShape(geometry.NewPoint(15, 5), geometry.NewPoint(25, 5), geometry.NewPoint(25, 15), geometry.NewPoint(15, 15)), geometry.NewShape(geometry.NewPoint(15, 15), geometry.NewPoint(25, 15), geometry.NewPoint(25, 25), geometry.NewPoint(15, 25))) + for _, shape := range walkable { + for _, edge := range shape.Edges() { + sx, bx := maths.MinMax(edge.GetStart().GetX(), edge.GetEnd().GetX()) + sy, by := maths.MinMax(edge.GetStart().GetY(), edge.GetEnd().GetY()) + for x := sx; x <= bx; x++ { + for y := sy; y <= by; y++ { + fp.Put(geometry.NewPoint[int](x, y), '+') + } + } + } + } + nm := navmesh.NewNavMesh(walkable, 0) + path := nm.FindPath(geometry.NewPoint(6, 6), geometry.NewPoint(18, 24)) + for _, point := range path { + fp.Put(geometry.NewPoint(point.GetX(), point.GetY()), 'G') + } + fmt.Println(fp) +} + +``` + +*** diff --git a/utils/hub/README.md b/utils/hub/README.md new file mode 100644 index 0000000..3bb9577 --- /dev/null +++ b/utils/hub/README.md @@ -0,0 +1,178 @@ +# Hub + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewObjectPool](#NewObjectPool)|创建一个 ObjectPool + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[ObjectPool](#objectpool)|基于 sync.Pool 实现的线程安全的对象池 + +
+ + +*** +## 详情信息 +#### func NewObjectPool(generator func () *T, releaser func (data *T)) *ObjectPool[*T] + +> 创建一个 ObjectPool + +示例代码: +```go + +func ExampleNewObjectPool() { + var p = hub.NewObjectPool[map[int]int](func() *map[int]int { + return &map[int]int{} + }, func(data *map[int]int) { + collection.ClearMap(*data) + }) + m := *p.Get() + m[1] = 1 + p.Release(&m) + fmt.Println(m) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewObjectPool(t *testing.T) { + var cases = []struct { + name string + generator func() *map[string]int + releaser func(data *map[string]int) + shouldPanic bool + }{{name: "TestNewObjectPool_NilGenerator", generator: nil, releaser: func(data *map[string]int) { + }, shouldPanic: true}, {name: "TestNewObjectPool_NilReleaser", generator: func() *map[string]int { + return &map[string]int{} + }, releaser: nil, shouldPanic: true}, {name: "TestNewObjectPool_NilGeneratorAndReleaser", generator: nil, releaser: nil, shouldPanic: true}, {name: "TestNewObjectPool_Normal", generator: func() *map[string]int { + return &map[string]int{} + }, releaser: func(data *map[string]int) { + }, shouldPanic: false}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if err := recover(); c.shouldPanic && err == nil { + t.Error("TestNewObjectPool should panic") + } + }() + _ = hub.NewObjectPool[map[string]int](c.generator, c.releaser) + }) + } +} + +``` + + +
+ + +*** +### ObjectPool `STRUCT` +基于 sync.Pool 实现的线程安全的对象池 + - 一些高频临时生成使用的对象可以通过 ObjectPool 进行管理,例如属性计算等 +```go +type ObjectPool[T any] struct { + p sync.Pool + releaser func(data T) +} +``` +#### func (*ObjectPool) Get() T +> 获取一个对象 +
+查看 / 收起单元测试 + + +```go + +func TestObjectPool_Get(t *testing.T) { + var cases = []struct { + name string + generator func() *map[string]int + releaser func(data *map[string]int) + }{{name: "TestObjectPool_Get_Normal", generator: func() *map[string]int { + return &map[string]int{} + }, releaser: func(data *map[string]int) { + for k := range *data { + delete(*data, k) + } + }}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + pool := hub.NewObjectPool[map[string]int](c.generator, c.releaser) + if actual := pool.Get(); len(*actual) != 0 { + t.Error("TestObjectPool_Get failed") + } + }) + } +} + +``` + + +
+ + +*** +#### func (*ObjectPool) Release(data T) +> 将使用完成的对象放回缓冲区 +
+查看 / 收起单元测试 + + +```go + +func TestObjectPool_Release(t *testing.T) { + var cases = []struct { + name string + generator func() *map[string]int + releaser func(data *map[string]int) + }{{name: "TestObjectPool_Release_Normal", generator: func() *map[string]int { + return &map[string]int{} + }, releaser: func(data *map[string]int) { + for k := range *data { + delete(*data, k) + } + }}} + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + pool := hub.NewObjectPool[map[string]int](c.generator, c.releaser) + msg := pool.Get() + m := *msg + m["test"] = 1 + pool.Release(msg) + if len(m) != 0 { + t.Error("TestObjectPool_Release failed") + } + }) + } +} + +``` + + +
+ + +*** diff --git a/utils/huge/README.md b/utils/huge/README.md new file mode 100644 index 0000000..54c94c7 --- /dev/null +++ b/utils/huge/README.md @@ -0,0 +1,379 @@ +# Huge + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewFloat](#NewFloat)|创建一个 Float +|[NewFloatByString](#NewFloatByString)|通过字符串创建一个 Float +|[NewInt](#NewInt)|创建一个 Int +|[NewIntByString](#NewIntByString)|通过字符串创建一个 Int + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Float](#float)|暂无描述... +|`STRUCT`|[Int](#int)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewFloat(x T) *Float + +> 创建一个 Float + +*** +#### func NewFloatByString(i string) *Float + +> 通过字符串创建一个 Float +> - 如果字符串不是一个合法的数字,则返回 0 + +*** +#### func NewInt(x T) *Int + +> 创建一个 Int + +*** +#### func NewIntByString(i string) *Int + +> 通过字符串创建一个 Int +> - 如果字符串不是一个合法的数字,则返回 0 + +*** +### Float `STRUCT` + +```go +type Float big.Float +``` +#### func (*Float) Copy() *Float +*** +#### func (*Float) Set(i *Float) *Float +*** +#### func (*Float) IsZero() bool +*** +#### func (*Float) ToBigFloat() *big.Float +*** +#### func (*Float) Cmp(i *Float) int +> 比较,当 slf > i 时返回 1,当 slf < i 时返回 -1,当 slf == i 时返回 0 +*** +#### func (*Float) GreaterThan(i *Float) bool +> 大于 +*** +#### func (*Float) GreaterThanOrEqualTo(i *Float) bool +> 大于或等于 +*** +#### func (*Float) LessThan(i *Float) bool +> 小于 +*** +#### func (*Float) LessThanOrEqualTo(i *Float) bool +> 小于或等于 +*** +#### func (*Float) EqualTo(i *Float) bool +> 等于 +*** +#### func (*Float) Float64() float64 +*** +#### func (*Float) String() string +*** +#### func (*Float) Add(i *Float) *Float +*** +#### func (*Float) Sub(i *Float) *Float +*** +#### func (*Float) Mul(i *Float) *Float +*** +#### func (*Float) Div(i *Float) *Float +*** +#### func (*Float) Sqrt() *Float +> 平方根 +*** +#### func (*Float) Abs() *Float +> 返回数字的绝对值 +*** +#### func (*Float) Sign() int +> 返回数字的符号 +> - 1:正数 +> - 0:零 +> - -1:负数 +*** +#### func (*Float) IsPositive() bool +> 是否为正数 +*** +#### func (*Float) IsNegative() bool +> 是否为负数 +*** +### Int `STRUCT` + +```go +type Int big.Int +``` +#### func (*Int) Copy() *Int +*** +#### func (*Int) Set(i *Int) *Int +*** +#### func (*Int) SetInt(i int) *Int +*** +#### func (*Int) SetInt8(i int8) *Int +*** +#### func (*Int) SetInt16(i int16) *Int +*** +#### func (*Int) SetInt32(i int32) *Int +*** +#### func (*Int) SetInt64(i int64) *Int +*** +#### func (*Int) SetUint(i uint) *Int +*** +#### func (*Int) SetUint8(i uint8) *Int +*** +#### func (*Int) SetUint16(i uint16) *Int +*** +#### func (*Int) SetUint32(i uint32) *Int +*** +#### func (*Int) SetUint64(i uint64) *Int +*** +#### func (*Int) IsZero() bool +*** +#### func (*Int) ToBigint() *big.Int +*** +#### func (*Int) Cmp(i *Int) int +> 比较,当 slf > i 时返回 1,当 slf < i 时返回 -1,当 slf == i 时返回 0 +*** +#### func (*Int) GreaterThan(i *Int) bool +> 大于 +*** +#### func (*Int) GreaterThanOrEqualTo(i *Int) bool +> 大于或等于 +*** +#### func (*Int) LessThan(i *Int) bool +> 小于 +*** +#### func (*Int) LessThanOrEqualTo(i *Int) bool +> 小于或等于 +*** +#### func (*Int) EqualTo(i *Int) bool +> 等于 +*** +#### func (*Int) Int64() int64 +*** +#### func (*Int) String() string +*** +#### func (*Int) Add(i *Int) *Int +*** +#### func (*Int) AddInt(i int) *Int +*** +#### func (*Int) AddInt8(i int8) *Int +*** +#### func (*Int) AddInt16(i int16) *Int +*** +#### func (*Int) AddInt32(i int32) *Int +*** +#### func (*Int) AddInt64(i int64) *Int +*** +#### func (*Int) AddUint(i uint) *Int +*** +#### func (*Int) AddUint8(i uint8) *Int +*** +#### func (*Int) AddUint16(i uint16) *Int +*** +#### func (*Int) AddUint32(i uint32) *Int +*** +#### func (*Int) AddUint64(i uint64) *Int +*** +#### func (*Int) Mul(i *Int) *Int +*** +#### func (*Int) MulInt(i int) *Int +*** +#### func (*Int) MulInt8(i int8) *Int +*** +#### func (*Int) MulInt16(i int16) *Int +*** +#### func (*Int) MulInt32(i int32) *Int +*** +#### func (*Int) MulInt64(i int64) *Int +*** +#### func (*Int) MulUint(i uint) *Int +*** +#### func (*Int) MulUint8(i uint8) *Int +*** +#### func (*Int) MulUint16(i uint16) *Int +*** +#### func (*Int) MulUint32(i uint32) *Int +*** +#### func (*Int) MulUint64(i uint64) *Int +*** +#### func (*Int) Sub(i *Int) *Int +*** +#### func (*Int) SubInt(i int) *Int +*** +#### func (*Int) SubInt8(i int8) *Int +*** +#### func (*Int) SubInt16(i int16) *Int +*** +#### func (*Int) SubInt32(i int32) *Int +*** +#### func (*Int) SubInt64(i int64) *Int +*** +#### func (*Int) SubUint(i uint) *Int +*** +#### func (*Int) SubUint8(i uint8) *Int +*** +#### func (*Int) SubUint16(i uint16) *Int +*** +#### func (*Int) SubUint32(i uint32) *Int +*** +#### func (*Int) SubUint64(i uint64) *Int +*** +#### func (*Int) Div(i *Int) *Int +*** +#### func (*Int) DivInt(i int) *Int +*** +#### func (*Int) DivInt8(i int8) *Int +*** +#### func (*Int) DivInt16(i int16) *Int +*** +#### func (*Int) DivInt32(i int32) *Int +*** +#### func (*Int) DivInt64(i int64) *Int +*** +#### func (*Int) DivUint(i uint) *Int +*** +#### func (*Int) DivUint8(i uint8) *Int +*** +#### func (*Int) DivUint16(i uint16) *Int +*** +#### func (*Int) DivUint32(i uint32) *Int +*** +#### func (*Int) DivUint64(i uint64) *Int +*** +#### func (*Int) Mod(i *Int) *Int +*** +#### func (*Int) ModInt(i int) *Int +*** +#### func (*Int) ModInt8(i int8) *Int +*** +#### func (*Int) ModInt16(i int16) *Int +*** +#### func (*Int) ModInt32(i int32) *Int +*** +#### func (*Int) ModInt64(i int64) *Int +*** +#### func (*Int) ModUint(i uint) *Int +*** +#### func (*Int) ModUint8(i uint8) *Int +*** +#### func (*Int) ModUint16(i uint16) *Int +*** +#### func (*Int) ModUint32(i uint32) *Int +*** +#### func (*Int) ModUint64(i uint64) *Int +*** +#### func (*Int) Pow(i *Int) *Int +*** +#### func (*Int) PowInt(i int) *Int +*** +#### func (*Int) PowInt8(i int8) *Int +*** +#### func (*Int) PowInt16(i int16) *Int +*** +#### func (*Int) PowInt32(i int32) *Int +*** +#### func (*Int) PowInt64(i int64) *Int +*** +#### func (*Int) PowUint(i uint) *Int +*** +#### func (*Int) PowUint8(i uint8) *Int +*** +#### func (*Int) PowUint16(i uint16) *Int +*** +#### func (*Int) PowUint32(i uint32) *Int +*** +#### func (*Int) PowUint64(i uint64) *Int +*** +#### func (*Int) Lsh(i int) *Int +> 左移 +*** +#### func (*Int) Rsh(i int) *Int +> 右移 +*** +#### func (*Int) And(i *Int) *Int +> 与 +*** +#### func (*Int) AndNot(i *Int) *Int +> 与非 +*** +#### func (*Int) Or(i *Int) *Int +> 或 +*** +#### func (*Int) Xor(i *Int) *Int +> 异或 +*** +#### func (*Int) Not() *Int +> 非 +*** +#### func (*Int) Sqrt() *Int +> 平方根 +*** +#### func (*Int) GCD(i *Int) *Int +> 最大公约数 +*** +#### func (*Int) LCM(i *Int) *Int +> 最小公倍数 +*** +#### func (*Int) ModInverse(i *Int) *Int +> 模反元素 +*** +#### func (*Int) ModSqrt(i *Int) *Int +> 模平方根 +*** +#### func (*Int) BitLen() int +> 二进制长度 +*** +#### func (*Int) Bit(i int) uint +> 二进制位 +*** +#### func (*Int) SetBit(i int, v uint) *Int +> 设置二进制位 +*** +#### func (*Int) Neg() *Int +> 返回数字的相反数 +*** +#### func (*Int) Abs() *Int +> 返回数字的绝对值 +*** +#### func (*Int) Sign() int +> 返回数字的符号 +> - 1:正数 +> - 0:零 +> - -1:负数 +*** +#### func (*Int) IsPositive() bool +> 是否为正数 +*** +#### func (*Int) IsNegative() bool +> 是否为负数 +*** +#### func (*Int) IsEven() bool +> 是否为偶数 +*** +#### func (*Int) IsOdd() bool +> 是否为奇数 +*** +#### func (*Int) ProportionalCalc(proportional *Int, formula func (v *Int) *Int) *Int +> 比例计算,该函数会再 formula 返回值的基础上除以 proportional +> - formula 为计算公式,该公式的参数为调用该函数的 Int 的拷贝 +*** diff --git a/utils/leaderboard/README.md b/utils/leaderboard/README.md new file mode 100644 index 0000000..6be6bd6 --- /dev/null +++ b/utils/leaderboard/README.md @@ -0,0 +1,86 @@ +# Leaderboard + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewBinarySearch](#NewBinarySearch)|创建一个基于内存的二分查找排行榜 +|[WithBinarySearchCount](#WithBinarySearchCount)|通过限制排行榜竞争者数量来创建排行榜 +|[WithBinarySearchASC](#WithBinarySearchASC)|通过升序的方式创建排行榜 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[BinarySearch](#binarysearch)|暂无描述... +|`STRUCT`|[BinarySearchRankChangeEventHandle](#binarysearchrankchangeeventhandle)|暂无描述... +|`STRUCT`|[BinarySearchOption](#binarysearchoption)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewBinarySearch(options ...BinarySearchOption[CompetitorID, Score]) *BinarySearch[CompetitorID, Score] + +> 创建一个基于内存的二分查找排行榜 + +示例代码: +```go + +func ExampleNewBinarySearch() { + bs := leaderboard2.NewBinarySearch[string, int](leaderboard2.WithBinarySearchCount[string, int](10)) + fmt.Println(bs != nil) +} + +``` + +*** +#### func WithBinarySearchCount(rankCount int) BinarySearchOption[CompetitorID, Score] + +> 通过限制排行榜竞争者数量来创建排行榜 +> - 默认情况下允许100位竞争者 + +*** +#### func WithBinarySearchASC() BinarySearchOption[CompetitorID, Score] + +> 通过升序的方式创建排行榜 +> - 默认情况下为降序 + +*** +### BinarySearch `STRUCT` + +```go +type BinarySearch[CompetitorID comparable, Score generic.Ordered] struct { + *binarySearchEvent[CompetitorID, Score] + asc bool + rankCount int + competitors *mappings.SyncMap[CompetitorID, Score] + scores []*scoreItem[CompetitorID, Score] + rankChangeEventHandles []BinarySearchRankChangeEventHandle[CompetitorID, Score] + rankClearBeforeEventHandles []BinarySearchRankClearBeforeEventHandle[CompetitorID, Score] +} +``` +### BinarySearchRankChangeEventHandle `STRUCT` + +```go +type BinarySearchRankChangeEventHandle[CompetitorID comparable, Score generic.Ordered] func(leaderboard *BinarySearch[CompetitorID, Score], competitorId CompetitorID, oldRank int, oldScore Score) +``` +### BinarySearchOption `STRUCT` + +```go +type BinarySearchOption[CompetitorID comparable, Score generic.Ordered] func(list *BinarySearch[CompetitorID, Score]) +``` diff --git a/utils/log/README.md b/utils/log/README.md new file mode 100644 index 0000000..7980ed3 --- /dev/null +++ b/utils/log/README.md @@ -0,0 +1,427 @@ +# Log + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[CallerBasicFormat](#CallerBasicFormat)|返回调用者的基本格式 +|[Println](#Println)|暂无描述... +|[Default](#Default)|获取默认的日志记录器 +|[SetDefault](#SetDefault)|设置默认的日志记录器 +|[SetDefaultBySlog](#SetDefaultBySlog)|设置默认的日志记录器 +|[Debug](#Debug)|在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[Info](#Info)|在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[Warn](#Warn)|在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[Error](#Error)|在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[DPanic](#DPanic)|在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[Panic](#Panic)|在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[Fatal](#Fatal)|在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +|[Skip](#Skip)|构造一个无操作字段,这在处理其他 Field 构造函数中的无效输入时通常很有用 +|[Duration](#Duration)|使用给定的键和值构造一个字段。编码器控制持续时间的序列化方式 +|[DurationP](#DurationP)|构造一个带有 time.Duration 的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Bool](#Bool)|构造一个带有布尔值的字段 +|[BoolP](#BoolP)|构造一个带有布尔值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[String](#String)|构造一个带有字符串值的字段 +|[StringP](#StringP)|构造一个带有字符串值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Int](#Int)|构造一个带有整数值的字段 +|[IntP](#IntP)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Int8](#Int8)|构造一个带有整数值的字段 +|[Int8P](#Int8P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Int16](#Int16)|构造一个带有整数值的字段 +|[Int16P](#Int16P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Int32](#Int32)|构造一个带有整数值的字段 +|[Int32P](#Int32P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Int64](#Int64)|构造一个带有整数值的字段 +|[Int64P](#Int64P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Uint](#Uint)|构造一个带有整数值的字段 +|[UintP](#UintP)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Uint8](#Uint8)|构造一个带有整数值的字段 +|[Uint8P](#Uint8P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Uint16](#Uint16)|构造一个带有整数值的字段 +|[Uint16P](#Uint16P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Uint32](#Uint32)|构造一个带有整数值的字段 +|[Uint32P](#Uint32P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Uint64](#Uint64)|构造一个带有整数值的字段 +|[Uint64P](#Uint64P)|构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Float](#Float)|构造一个带有浮点值的字段 +|[FloatP](#FloatP)|构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Float32](#Float32)|构造一个带有浮点值的字段 +|[Float32P](#Float32P)|构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Float64](#Float64)|构造一个带有浮点值的字段 +|[Float64P](#Float64P)|构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Time](#Time)|构造一个带有时间值的字段 +|[TimeP](#TimeP)|构造一个带有时间值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" +|[Any](#Any)|构造一个带有任意值的字段 +|[Group](#Group)|返回分组字段 +|[Stack](#Stack)|返回堆栈字段 +|[Err](#Err)|构造一个带有错误值的字段 +|[NewHandler](#NewHandler)|创建一个更偏向于人类可读的处理程序,该处理程序也是默认的处理程序 +|[NewLogger](#NewLogger)|创建一个新的日志记录器 +|[NewMultiHandler](#NewMultiHandler)|创建一个新的多处理程序 +|[NewOptions](#NewOptions)|创建一个新的日志选项 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Field](#field)|暂无描述... +|`STRUCT`|[Logger](#logger)|暂无描述... +|`STRUCT`|[MultiHandler](#multihandler)|暂无描述... +|`STRUCT`|[Option](#option)|暂无描述... + +
+ + +*** +## 详情信息 +#### func CallerBasicFormat(file string, line int) (repFile string, refLine string) + +> 返回调用者的基本格式 + +*** +#### func Println(str string, color string, desc string) + + +*** +#### func Default() *Logger + +> 获取默认的日志记录器 + +*** +#### func SetDefault(l *Logger) + +> 设置默认的日志记录器 + +*** +#### func SetDefaultBySlog(l *slog.Logger) + +> 设置默认的日志记录器 + +*** +#### func Debug(msg string, args ...any) + +> 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 + +*** +#### func Info(msg string, args ...any) + +> 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 + +*** +#### func Warn(msg string, args ...any) + +> 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 + +*** +#### func Error(msg string, args ...any) + +> 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 + +*** +#### func DPanic(msg string, args ...any) + +> 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +> - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“development panic”)。这对于捕获可恢复但不应该发生的错误很有用 +> - 该 panic 仅在 NewHandler 中创建的处理器会生效 + +*** +#### func Panic(msg string, args ...any) + +> 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +> - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic + +*** +#### func Fatal(msg string, args ...any) + +> 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +> - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用 + +*** +#### func Skip(vs ...any) slog.Attr + +> 构造一个无操作字段,这在处理其他 Field 构造函数中的无效输入时通常很有用 +> - 该函数还支持将其他字段快捷的转换为 Skip 字段 + +*** +#### func Duration(key string, val time.Duration) slog.Attr + +> 使用给定的键和值构造一个字段。编码器控制持续时间的序列化方式 + +*** +#### func DurationP(key string, val *time.Duration) slog.Attr + +> 构造一个带有 time.Duration 的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Bool(key string, val bool) slog.Attr + +> 构造一个带有布尔值的字段 + +*** +#### func BoolP(key string, val *bool) slog.Attr + +> 构造一个带有布尔值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func String(key string, val string) slog.Attr + +> 构造一个带有字符串值的字段 + +*** +#### func StringP(key string, val *string) slog.Attr + +> 构造一个带有字符串值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Int(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func IntP(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Int8(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Int8P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Int16(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Int16P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Int32(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Int32P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Int64(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Int64P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Uint(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func UintP(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Uint8(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Uint8P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Uint16(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Uint16P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Uint32(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Uint32P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Uint64(key string, val I) slog.Attr + +> 构造一个带有整数值的字段 + +*** +#### func Uint64P(key string, val *I) slog.Attr + +> 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Float(key string, val F) slog.Attr + +> 构造一个带有浮点值的字段 + +*** +#### func FloatP(key string, val *F) slog.Attr + +> 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Float32(key string, val F) slog.Attr + +> 构造一个带有浮点值的字段 + +*** +#### func Float32P(key string, val *F) slog.Attr + +> 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Float64(key string, val F) slog.Attr + +> 构造一个带有浮点值的字段 + +*** +#### func Float64P(key string, val *F) slog.Attr + +> 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Time(key string, val time.Time) slog.Attr + +> 构造一个带有时间值的字段 + +*** +#### func TimeP(key string, val *time.Time) slog.Attr + +> 构造一个带有时间值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null" + +*** +#### func Any(key string, val any) slog.Attr + +> 构造一个带有任意值的字段 + +*** +#### func Group(key string, args ...any) slog.Attr + +> 返回分组字段 + +*** +#### func Stack(key string) slog.Attr + +> 返回堆栈字段 + +
+查看 / 收起单元测试 + + +```go + +func TestStack(t *testing.T) { + var i int + for { + time.Sleep(time.Second) + Debug("TestStack") + Info("TestStack") + Warn("TestStack") + Error("TestStack") + i++ + if i == 3 { + Default().Logger.Handler().(*handler).opts.GerRuntimeHandler().ChangeLevel(slog.LevelInfo) + } + } +} + +``` + + +
+ + +*** +#### func Err(err error) slog.Attr + +> 构造一个带有错误值的字段 + +*** +#### func NewHandler(w io.Writer, opts *Options) slog.Handler + +> 创建一个更偏向于人类可读的处理程序,该处理程序也是默认的处理程序 + +*** +#### func NewLogger(handlers ...slog.Handler) *Logger + +> 创建一个新的日志记录器 + +*** +#### func NewMultiHandler(handlers ...slog.Handler) slog.Handler + +> 创建一个新的多处理程序 + +*** +#### func NewOptions() *Options + +> 创建一个新的日志选项 + +*** +### Field `STRUCT` + +```go +type Field slog.Attr +``` +### Logger `STRUCT` + +```go +type Logger struct { + *slog.Logger +} +``` +### MultiHandler `STRUCT` + +```go +type MultiHandler struct { + handlers []slog.Handler +} +``` +#### func (MultiHandler) Enabled(ctx context.Context, level slog.Level) bool +*** +#### func (MultiHandler) Handle(ctx context.Context, record slog.Record) (err error) +*** +#### func (MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler +*** +#### func (MultiHandler) WithGroup(name string) slog.Handler +*** +### Option `STRUCT` + +```go +type Option func(opts *Options) +``` diff --git a/utils/log/survey/README.md b/utils/log/survey/README.md new file mode 100644 index 0000000..7c2e721 --- /dev/null +++ b/utils/log/survey/README.md @@ -0,0 +1,316 @@ +# Survey + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewFileFlusher](#NewFileFlusher)|创建一个文件刷新器 +|[WithFlushInterval](#WithFlushInterval)|设置日志文件刷新间隔 +|[Reg](#Reg)|注册一个运营日志记录器 +|[Record](#Record)|记录一条运营日志 +|[RecordBytes](#RecordBytes)|记录一条运营日志 +|[Flush](#Flush)|将运营日志记录器的缓冲区数据写入到文件 +|[Close](#Close)|关闭运营日志记录器 +|[Analyze](#Analyze)|分析特定文件的记录,当发生错误时,会发生 panic +|[AnalyzeMulti](#AnalyzeMulti)|与 Analyze 类似,但是可以分析多个文件 +|[IncrementAnalyze](#IncrementAnalyze)|增量分析,返回一个函数,每次调用该函数都会分析文件中新增的内容 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Analyzer](#analyzer)|分析器 +|`INTERFACE`|[Flusher](#flusher)|用于刷新缓冲区的接口 +|`STRUCT`|[FileFlusher](#fileflusher)|暂无描述... +|`STRUCT`|[Option](#option)|选项 +|`STRUCT`|[Result](#result)|暂无描述... +|`STRUCT`|[R](#r)|记录器所记录的一条数据 +|`STRUCT`|[Report](#report)|分析报告 + +
+ + +*** +## 详情信息 +#### func NewFileFlusher(filePath string, layout ...string) *FileFlusher + +> 创建一个文件刷新器 +> - layout 为日志文件名的时间戳格式 (默认为 time.DateOnly) + +*** +#### func WithFlushInterval(interval time.Duration) Option + +> 设置日志文件刷新间隔 +> - 默认为 3s,当日志文件刷新间隔 <= 0 时,将会在每次写入日志时刷新日志文件 + +*** +#### func Reg(name string, flusher Flusher, options ...Option) + +> 注册一个运营日志记录器 + +*** +#### func Record(name string, data map[string]any) + +> 记录一条运营日志 + +*** +#### func RecordBytes(name string, data []byte) + +> 记录一条运营日志 + +*** +#### func Flush(names ...string) + +> 将运营日志记录器的缓冲区数据写入到文件 +> - name 为空时,将所有记录器的缓冲区数据写入到文件 + +*** +#### func Close(names ...string) + +> 关闭运营日志记录器 + +*** +#### func Analyze(filePath string, handle func (analyzer *Analyzer, record R)) *Report + +> 分析特定文件的记录,当发生错误时,会发生 panic +> - handle 为并行执行的,需要自行处理并发安全 +> - 适用于外部进程对于日志文件的读取,但是需要注意的是,此时日志文件可能正在被写入,所以可能会读取到错误的数据 + +*** +#### func AnalyzeMulti(filePaths []string, handle func (analyzer *Analyzer, record R)) *Report + +> 与 Analyze 类似,但是可以分析多个文件 + +*** +#### func IncrementAnalyze(filePath string, handle func (analyzer *Analyzer, record R)) func () ( *Report, error) + +> 增量分析,返回一个函数,每次调用该函数都会分析文件中新增的内容 + +
+查看 / 收起单元测试 + + +```go + +func TestIncrementAnalyze(t *testing.T) { + path := `./test/day.2023-09-06.log` + reader := survey.IncrementAnalyze(path, func(analyzer *survey.Analyzer, record survey.R) { + switch record.GetString("type") { + case "open_conn": + analyzer.IncreaseValueNonRepeat("开播人数", record, 1, "live_id") + case "report_rank": + analyzer.IncreaseValue("开始游戏次数", 1) + analyzer.Increase("开播时长", record, "game_time") + analyzer.Sub(record.GetString("live_id")).IncreaseValue("开始游戏次数", 1) + analyzer.Sub(record.GetString("live_id")).Increase("开播时长", record, "game_time") + case "statistics": + analyzer.IncreaseValueNonRepeat("活跃人数", record, 1, "active_player") + analyzer.IncreaseValueNonRepeat("评论人数", record, 1, "comment_player") + analyzer.IncreaseValueNonRepeat("点赞人数", record, 1, "like_player") + analyzer.Sub(record.GetString("live_id")).IncreaseValueNonRepeat("活跃人数", record, 1, "active_player") + analyzer.Sub(record.GetString("live_id")).IncreaseValueNonRepeat("评论人数", record, 1, "comment_player") + analyzer.Sub(record.GetString("live_id")).IncreaseValueNonRepeat("点赞人数", record, 1, "like_player") + giftId := record.GetString("gift.gift_id") + if len(giftId) > 0 { + giftPrice := record.GetFloat64("gift.price") + giftCount := record.GetFloat64("gift.count") + giftSender := record.GetString("gift.gift_senders") + analyzer.IncreaseValue("礼物总价值", giftPrice*giftCount) + analyzer.IncreaseValueNonRepeat(fmt.Sprintf("送礼人数_%s", giftId), record, 1, giftSender) + analyzer.IncreaseValue(fmt.Sprintf("礼物总数_%s", giftId), giftCount) + analyzer.Sub(record.GetString("live_id")).IncreaseValue("礼物总价值", giftPrice*giftCount) + analyzer.Sub(record.GetString("live_id")).IncreaseValueNonRepeat(fmt.Sprintf("送礼人数_%s", giftId), record, 1, giftSender) + analyzer.Sub(record.GetString("live_id")).IncreaseValue(fmt.Sprintf("礼物总数_%s", giftId), giftCount) + } + } + }) + for i := 0; i < 10; i++ { + report, err := reader() + if err != nil { + t.Fatal(err) + } + fmt.Println(report.FilterSub("warzone0009")) + } +} + +``` + + +
+ + +*** +### Analyzer `STRUCT` +分析器 +```go +type Analyzer struct { + v map[string]any + vc map[string]int64 + repeat map[string]struct{} + subs map[string]*Analyzer + format map[string]func(v any) any + m sync.Mutex +} +``` +#### func (*Analyzer) Sub(key string) *Analyzer +> 获取子分析器 +*** +#### func (*Analyzer) SetFormat(key string, format func (v any) any) +> 设置格式化函数 +*** +#### func (*Analyzer) SetValueIfGreaterThan(key string, value float64) +> 设置指定 key 的值,当新值大于旧值时 +> - 当已有值不为 float64 时,将会被忽略 +*** +#### func (*Analyzer) SetValueIfLessThan(key string, value float64) +> 设置指定 key 的值,当新值小于旧值时 +> - 当已有值不为 float64 时,将会被忽略 +*** +#### func (*Analyzer) SetValueIf(key string, expression bool, value float64) +> 当表达式满足的时候将设置指定 key 的值为 value +*** +#### func (*Analyzer) SetValueStringIf(key string, expression bool, value string) +> 当表达式满足的时候将设置指定 key 的值为 value +*** +#### func (*Analyzer) SetValue(key string, value float64) +> 设置指定 key 的值 +*** +#### func (*Analyzer) SetValueString(key string, value string) +> 设置指定 key 的值 +*** +#### func (*Analyzer) Increase(key string, record R, recordKey string) +> 在指定 key 现有值的基础上增加 recordKey 的值 +> - 当分析器已经记录过相同 key 的值时,会根据已有的值类型进行不同处理 +> +> 处理方式: +> - 当已有值类型为 string 时,将会使用新的值的 string 类型进行覆盖 +> - 当已有值类型为 float64 时,当新的值类型不为 float64 时,将会被忽略 +*** +#### func (*Analyzer) IncreaseValue(key string, value float64) +> 在指定 key 现有值的基础上增加 value +*** +#### func (*Analyzer) IncreaseNonRepeat(key string, record R, recordKey string, dimension ...string) +> 在指定 key 现有值的基础上增加 recordKey 的值,但是当去重维度 dimension 相同时,不会增加 +*** +#### func (*Analyzer) IncreaseValueNonRepeat(key string, record R, value float64, dimension ...string) +> 在指定 key 现有值的基础上增加 value,但是当去重维度 dimension 相同时,不会增加 +*** +#### func (*Analyzer) GetValue(key string) float64 +> 获取当前记录的值 +*** +#### func (*Analyzer) GetValueString(key string) string +> 获取当前记录的值 +*** +### Flusher `INTERFACE` +用于刷新缓冲区的接口 +```go +type Flusher interface { + Flush(records []string) + Info() string +} +``` +### FileFlusher `STRUCT` + +```go +type FileFlusher struct { + dir string + fn string + fe string + layout string + layoutLen int +} +``` +#### func (*FileFlusher) Flush(records []string) +*** +#### func (*FileFlusher) Info() string +*** +### Option `STRUCT` +选项 +```go +type Option func(logger *logger) +``` +### Result `STRUCT` + +```go +type Result gjson.Result +``` +### R `STRUCT` +记录器所记录的一条数据 +```go +type R string +``` +#### func (R) GetTime(layout string) time.Time +> 获取该记录的时间 +*** +#### func (R) Get(key string) Result +> 获取指定 key 的值 +> - 当 key 为嵌套 key 时,使用 . 进行分割,例如:a.b.c +> - 更多用法参考:https://github.com/tidwall/gjson +*** +#### func (R) Exist(key string) bool +> 判断指定 key 是否存在 +*** +#### func (R) GetString(key string) string +> 该函数为 Get(key).String() 的简写 +*** +#### func (R) GetInt64(key string) int64 +> 该函数为 Get(key).Int() 的简写 +*** +#### func (R) GetInt(key string) int +> 该函数为 Get(key).Int() 的简写,但是返回值为 int 类型 +*** +#### func (R) GetFloat64(key string) float64 +> 该函数为 Get(key).Float() 的简写 +*** +#### func (R) GetBool(key string) bool +> 该函数为 Get(key).Bool() 的简写 +*** +#### func (R) String() string +*** +### Report `STRUCT` +分析报告 +```go +type Report struct { + analyzer *Analyzer + Name string + Values map[string]any + Counter map[string]int64 + Subs []*Report +} +``` +#### func (*Report) Avg(key string) float64 +> 计算平均值 +*** +#### func (*Report) Count(key string) int64 +> 获取特定 key 的计数次数 +*** +#### func (*Report) Sum(keys ...string) float64 +> 获取特定 key 的总和 +*** +#### func (*Report) Sub(name string) *Report +> 获取特定名称的子报告 +*** +#### func (*Report) ReserveSubByPrefix(prefix string) *Report +> 仅保留特定前缀的子报告 +*** +#### func (*Report) ReserveSub(names ...string) *Report +> 仅保留特定名称子报告 +*** +#### func (*Report) FilterSub(names ...string) *Report +> 将特定名称的子报告过滤掉 +*** +#### func (*Report) String() string +*** diff --git a/utils/maths/README.md b/utils/maths/README.md new file mode 100644 index 0000000..004d1fe --- /dev/null +++ b/utils/maths/README.md @@ -0,0 +1,220 @@ +# Maths + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Compare](#Compare)|根据特定表达式比较两个值 +|[IsContinuity](#IsContinuity)|检查一组值是否连续 +|[IsContinuityWithSort](#IsContinuityWithSort)|检查一组值排序后是否连续 +|[GetDefaultTolerance](#GetDefaultTolerance)|获取默认误差范围 +|[Pow](#Pow)|整数幂运算 +|[PowInt64](#PowInt64)|整数幂运算 +|[Min](#Min)|返回两个数之中较小的值 +|[Max](#Max)|返回两个数之中较大的值 +|[MinMax](#MinMax)|将两个数按照较小的和较大的顺序进行返回 +|[MaxMin](#MaxMin)|将两个数按照较大的和较小的顺序进行返回 +|[Clamp](#Clamp)|将给定值限制在最小值和最大值之间 +|[Tolerance](#Tolerance)|检查两个值是否在一个误差范围内 +|[Merge](#Merge)|通过一个参考值合并两个数字 +|[UnMerge](#UnMerge)|通过一个参考值取消合并的两个数字 +|[MergeToInt64](#MergeToInt64)|将两个数字合并为一个 int64 数字 +|[UnMergeInt64](#UnMergeInt64)|将一个 int64 数字拆分为两个数字 +|[ToContinuous](#ToContinuous)|将一组非连续的数字转换为从1开始的连续数字 +|[CountDigits](#CountDigits)|接收一个整数 num 作为输入,并返回该数字的位数 +|[GetDigitValue](#GetDigitValue)|接收一个整数 num 和一个表示目标位数的整数 digit 作为输入,并返 +|[JoinNumbers](#JoinNumbers)|将一组数字连接起来 +|[IsOdd](#IsOdd)|返回 n 是否为奇数 +|[IsEven](#IsEven)|返回 n 是否为偶数 +|[MakeLastDigitsZero](#MakeLastDigitsZero)|返回一个新的数,其中 num 的最后 digits 位数被设为零。 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[CompareExpression](#compareexpression)|比较表达式 + +
+ + +*** +## 详情信息 +#### func Compare(a V, expression CompareExpression, b V) bool + +> 根据特定表达式比较两个值 + +*** +#### func IsContinuity(values S) bool + +> 检查一组值是否连续 + +示例代码: +```go + +func ExampleIsContinuity() { + fmt.Println(maths.IsContinuity([]int{1, 2, 3, 4, 5, 6, 7})) + fmt.Println(maths.IsContinuity([]int{1, 2, 3, 5, 5, 6, 7})) +} + +``` + +*** +#### func IsContinuityWithSort(values S) bool + +> 检查一组值排序后是否连续 + +*** +#### func GetDefaultTolerance() float64 + +> 获取默认误差范围 + +*** +#### func Pow(a int, n int) int + +> 整数幂运算 + +*** +#### func PowInt64(a int64, n int64) int64 + +> 整数幂运算 + +*** +#### func Min(a V, b V) V + +> 返回两个数之中较小的值 + +*** +#### func Max(a V, b V) V + +> 返回两个数之中较大的值 + +*** +#### func MinMax(a V, b V) (min V, max V) + +> 将两个数按照较小的和较大的顺序进行返回 + +*** +#### func MaxMin(a V, b V) (max V, min V) + +> 将两个数按照较大的和较小的顺序进行返回 + +*** +#### func Clamp(value V, min V, max V) V + +> 将给定值限制在最小值和最大值之间 + +*** +#### func Tolerance(value1 V, value2 V, tolerance V) bool + +> 检查两个值是否在一个误差范围内 + +*** +#### func Merge(refer V, a V, b V) V + +> 通过一个参考值合并两个数字 + +*** +#### func UnMerge(refer V, num V) (a V, b V) + +> 通过一个参考值取消合并的两个数字 + +*** +#### func MergeToInt64(v1 V, v2 V) int64 + +> 将两个数字合并为一个 int64 数字 + +*** +#### func UnMergeInt64(n int64) V, V + +> 将一个 int64 数字拆分为两个数字 + +*** +#### func ToContinuous(nums S) map[V]V + +> 将一组非连续的数字转换为从1开始的连续数字 +> - 返回值是一个 map,key 是从 1 开始的连续数字,value 是原始数字 + +示例代码: +```go + +func ExampleToContinuous() { + var nums = []int{1, 2, 3, 5, 6, 7, 9, 10, 11} + var continuous = maths.ToContinuous(nums) + fmt.Println(nums) + fmt.Println(continuous) +} + +``` + +*** +#### func CountDigits(num V) int + +> 接收一个整数 num 作为输入,并返回该数字的位数 + +*** +#### func GetDigitValue(num int64, digit int) int64 + +> 接收一个整数 num 和一个表示目标位数的整数 digit 作为输入,并返 +> 回数字 num 在指定位数上的数值。我们使用 math.Abs() 函数获取 num 的绝对值,并通 +> 过除以10的操作将 num 移动到目标位数上。然后,通过取余运算得到位数上的数值 + +*** +#### func JoinNumbers(num1 V, n ...V) V + +> 将一组数字连接起来 + +*** +#### func IsOdd(n V) bool + +> 返回 n 是否为奇数 + +*** +#### func IsEven(n V) bool + +> 返回 n 是否为偶数 + +*** +#### func MakeLastDigitsZero(num T, digits int) T + +> 返回一个新的数,其中 num 的最后 digits 位数被设为零。 +> - 函数首先创建一个 10 的 digits 次方的遮罩,然后通过整除和乘以这个遮罩来使 num 的最后 digits 位归零。 +> - 当 T 类型为浮点型时,将被向下取整后再进行转换 + +
+查看 / 收起单元测试 + + +```go + +func TestMakeLastDigitsZero(t *testing.T) { + for i := 0; i < 20; i++ { + n := float64(random.Int64(100, 999999)) + t.Log(n, 3, maths.MakeLastDigitsZero(n, 3)) + } +} + +``` + + +
+ + +*** +### CompareExpression `STRUCT` +比较表达式 +```go +type CompareExpression int +``` diff --git a/utils/memory/README.md b/utils/memory/README.md new file mode 100644 index 0000000..4d1c180 --- /dev/null +++ b/utils/memory/README.md @@ -0,0 +1,115 @@ +# Memory + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Run](#Run)|运行持久化缓存程序 +|[BindPersistCacheProgram](#BindPersistCacheProgram)|绑定持久化缓存程序 +|[BindAction](#BindAction)|绑定需要缓存的操作函数 +|[NewOption](#NewOption)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Option](#option)|暂无描述... + +
+ + +*** +## 详情信息 +#### func Run() + +> 运行持久化缓存程序 + +*** +#### func BindPersistCacheProgram(name string, handler OutputParamHandlerFunc, option ...*Option) func () + +> 绑定持久化缓存程序 +> - name 持久化缓存程序名称 +> - handler 持久化缓存程序处理函数 +> - option 持久化缓存程序选项 +> +> 注意事项: +> - 持久化程序建议声明为全局变量进行使用 +> - 持久化程序处理函数参数类型必须与绑定的缓存程序输出参数类型一致,并且相同 name 的持久化程序必须在 BindAction 之后进行绑定 +> - 默认情况下只有执行该函数返回的函数才会进行持久化,如果需要持久化策略,可以设置 option 参数或者自行实现策略调用返回的函数 +> - 所有持久化程序绑定完成后,应该主动调用 Run 函数运行 + +*** +#### func BindAction(name string, handler Func) Func + +> 绑定需要缓存的操作函数 +> - name 缓存操作名称 +> - handler 缓存操作处理函数 +> +> 注意事项: +> - 关于持久化缓存程序的绑定请参考 BindPersistCacheProgram +> - handler 函数的返回值将被作为缓存目标,如果返回值为非指针类型,将可能会发生意外的情况 +> - 当传入的 handler 没有任何返回值时,将不会被缓存,并且不会占用缓存操作名称 +> +> 使用场景: +> - 例如在游戏中,需要根据玩家 ID 查询玩家信息,可以使用该函数进行绑定,当查询玩家信息时,如果缓存中存在该玩家信息,将直接返回缓存中的数据,否则将执行 handler 函数进行查询并缓存 + +
+查看 / 收起单元测试 + + +```go + +func TestBindAction(t *testing.T) { + var player *Player + player = QueryPlayer(1) + fmt.Println(player.ID) + player.ID = 666 + player = QueryPlayer(1) + fmt.Println(player.ID) + player = QueryPlayer(2) + fmt.Println(player.ID) + QueryPlayerPersist() + time.Sleep(times.Week) +} + +``` + + +
+ + +*** +#### func NewOption() *Option + + +*** +### Option `STRUCT` + +```go +type Option struct { + ticker *timer.Ticker + firstDelay time.Duration + interval time.Duration + delay time.Duration +} +``` +#### func (*Option) WithPeriodicity(ticker *timer.Ticker, firstDelay time.Duration, interval time.Duration, delay time.Duration) *Option +> 设置持久化周期 +> - ticker 定时器,通常建议使用服务器的定时器,这样可以降低多线程的程序复杂性 +> - firstDelay 首次持久化延迟,当首次持久化为 0 时,将会在下一个持久化周期开始时持久化 +> - interval 持久化间隔 +> - delay 每条数据持久化间隔,适当的设置该值可以使持久化期间尽量降低对用户体验的影响,如果为0,将会一次性持久化所有数据 +*** diff --git a/utils/moving/README.md b/utils/moving/README.md new file mode 100644 index 0000000..e243217 --- /dev/null +++ b/utils/moving/README.md @@ -0,0 +1,135 @@ +# Moving + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewTwoDimensional](#NewTwoDimensional)|创建一个用于2D对象移动的实例(TwoDimensional) +|[WithTwoDimensionalTimeUnit](#WithTwoDimensionalTimeUnit)|通过特定时间单位创建 +|[WithTwoDimensionalIdleWaitTime](#WithTwoDimensionalIdleWaitTime)|通过特定的空闲等待时间创建 +|[WithTwoDimensionalInterval](#WithTwoDimensionalInterval)|通过特定的移动间隔时间创建 +|[NewEntity](#NewEntity)|暂无描述... + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[TwoDimensional](#twodimensional)|用于2D对象移动的数据结构 +|`INTERFACE`|[TwoDimensionalEntity](#twodimensionalentity)|2D移动对象接口定义 +|`STRUCT`|[Position2DChangeEventHandle](#position2dchangeeventhandle)|暂无描述... +|`STRUCT`|[TwoDimensionalOption](#twodimensionaloption)|暂无描述... + +
+ + +*** +## 详情信息 +#### func NewTwoDimensional(options ...TwoDimensionalOption[EID, PosType]) *TwoDimensional[EID, PosType] + +> 创建一个用于2D对象移动的实例(TwoDimensional) + +示例代码: +```go + +func ExampleNewTwoDimensional() { + m := moving2.NewTwoDimensional[int64, float64]() + defer func() { + m.Release() + }() + fmt.Println(m != nil) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestNewTwoDimensional(t *testing.T) { + m := moving2.NewTwoDimensional[int64, float64]() + defer func() { + m.Release() + }() +} + +``` + + +
+ + +*** +#### func WithTwoDimensionalTimeUnit(duration time.Duration) TwoDimensionalOption[EID, PosType] + +> 通过特定时间单位创建 +> - 默认单位为1毫秒,最小单位也为1毫秒 + +*** +#### func WithTwoDimensionalIdleWaitTime(duration time.Duration) TwoDimensionalOption[EID, PosType] + +> 通过特定的空闲等待时间创建 +> - 默认情况下在没有新的移动计划时将限制 100毫秒 + 移动间隔事件(默认100毫秒) + +*** +#### func WithTwoDimensionalInterval(duration time.Duration) TwoDimensionalOption[EID, PosType] + +> 通过特定的移动间隔时间创建 + +*** +#### func NewEntity(guid int64, speed float64) *MoveEntity + + +*** +### TwoDimensional `STRUCT` +用于2D对象移动的数据结构 + - 通过对象调用 MoveTo 方法后将开始执行该对象的移动 + - 移动将在根据设置的每次移动间隔时间(WithTwoDimensionalInterval)进行移动,当无对象移动需要移动时将会进入短暂的休眠 + - 当对象移动速度永久为0时,将会导致永久无法完成的移动 +```go +type TwoDimensional[EID generic.Basic, PosType generic.SignedNumber] struct { + rw sync.RWMutex + entities map[EID]*moving2DTarget[EID, PosType] + timeUnit float64 + idle time.Duration + interval time.Duration + close bool + position2DChangeEventHandles []Position2DChangeEventHandle[EID, PosType] + position2DDestinationEventHandles []Position2DDestinationEventHandle[EID, PosType] + position2DStopMoveEventHandles []Position2DStopMoveEventHandle[EID, PosType] +} +``` +### TwoDimensionalEntity `INTERFACE` +2D移动对象接口定义 +```go +type TwoDimensionalEntity[EID generic.Basic, PosType generic.SignedNumber] interface { + GetTwoDimensionalEntityID() EID + GetSpeed() float64 + GetPosition() geometry.Point[PosType] + SetPosition(geometry.Point[PosType]) +} +``` +### Position2DChangeEventHandle `STRUCT` + +```go +type Position2DChangeEventHandle[EID generic.Basic, PosType generic.SignedNumber] func(moving *TwoDimensional[EID, PosType], entity TwoDimensionalEntity[EID, PosType], oldX PosType) +``` +### TwoDimensionalOption `STRUCT` + +```go +type TwoDimensionalOption[EID generic.Basic, PosType generic.SignedNumber] func(moving *TwoDimensional[EID, PosType]) +``` diff --git a/utils/network/README.md b/utils/network/README.md new file mode 100644 index 0000000..40e9b93 --- /dev/null +++ b/utils/network/README.md @@ -0,0 +1,32 @@ +# Network + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[IP](#IP)|返回本机出站地址 + + + +
+ + +*** +## 详情信息 +#### func IP() (ip net.IP, err error) + +> 返回本机出站地址 + +*** diff --git a/utils/offset/README.md b/utils/offset/README.md new file mode 100644 index 0000000..3c35959 --- /dev/null +++ b/utils/offset/README.md @@ -0,0 +1,77 @@ +# Offset + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewTime](#NewTime)|新建一个包含偏移的时间 +|[SetGlobal](#SetGlobal)|设置全局偏移时间 +|[GetGlobal](#GetGlobal)|获取全局偏移时间 +|[Now](#Now)|获取当前时间偏移后的时间 +|[Since](#Since)|获取当前时间偏移后的时间自从 t 以来经过的时间 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Time](#time)|带有偏移量的时间 + +
+ + +*** +## 详情信息 +#### func NewTime(offset time.Duration) *Time + +> 新建一个包含偏移的时间 + +*** +#### func SetGlobal(offset time.Duration) + +> 设置全局偏移时间 + +*** +#### func GetGlobal() *Time + +> 获取全局偏移时间 + +*** +#### func Now() time.Time + +> 获取当前时间偏移后的时间 + +*** +#### func Since(t time.Time) time.Duration + +> 获取当前时间偏移后的时间自从 t 以来经过的时间 + +*** +### Time `STRUCT` +带有偏移量的时间 +```go +type Time struct { + offset time.Duration +} +``` +#### func (*Time) SetOffset(offset time.Duration) +> 设置时间偏移 +*** +#### func (*Time) Now() time.Time +> 获取当前时间偏移后的时间 +*** +#### func (*Time) Since(t time.Time) time.Duration +> 获取当前时间偏移后的时间自从 t 以来经过的时间 +*** diff --git a/utils/random/README.md b/utils/random/README.md new file mode 100644 index 0000000..e607042 --- /dev/null +++ b/utils/random/README.md @@ -0,0 +1,222 @@ +# Random + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Dice](#Dice)|掷骰子 +|[DiceN](#DiceN)|掷骰子 +|[NetIP](#NetIP)|返回一个随机的IP地址 +|[Port](#Port)|返回一个随机的端口号 +|[IPv4](#IPv4)|返回一个随机产生的IPv4地址。 +|[IPv4Port](#IPv4Port)|返回一个随机产生的IPv4地址和端口。 +|[Int64](#Int64)|返回一个介于min和max之间的int64类型的随机数。 +|[Int](#Int)|返回一个介于min和max之间的的int类型的随机数。 +|[Duration](#Duration)|返回一个介于min和max之间的的Duration类型的随机数。 +|[Float64](#Float64)|返回一个0~1的浮点数 +|[Float32](#Float32)|返回一个0~1的浮点数 +|[IntN](#IntN)|返回一个0~n的整数 +|[Bool](#Bool)|返回一个随机的布尔值 +|[ProbabilitySlice](#ProbabilitySlice)|按概率随机从切片中产生一个数据并返回命中的对象及是否未命中 +|[ProbabilitySliceIndex](#ProbabilitySliceIndex)|按概率随机从切片中产生一个数据并返回命中的对象及对象索引以及是否未命中 +|[Probability](#Probability)|输入一个概率,返回是否命中 +|[ProbabilityChooseOne](#ProbabilityChooseOne)|输入一组概率,返回命中的索引 +|[RefreshSeed](#RefreshSeed)|暂无描述... +|[ChineseName](#ChineseName)|返回一个随机组成的中文姓名。 +|[EnglishName](#EnglishName)|返回一个随机组成的英文姓名。 +|[Name](#Name)|返回一个随机组成的中文或英文姓名 +|[NumberString](#NumberString)|返回一个介于min和max之间的string类型的随机数。 +|[NumberStringRepair](#NumberStringRepair)|返回一个介于min和max之间的string类型的随机数 +|[HostName](#HostName)|返回一个随机产生的hostname。 +|[WeightSlice](#WeightSlice)|按权重随机从切片中产生一个数据并返回 +|[WeightSliceIndex](#WeightSliceIndex)|按权重随机从切片中产生一个数据并返回数据和对应索引 +|[WeightMap](#WeightMap)|按权重随机从map中产生一个数据并返回 +|[WeightMapKey](#WeightMapKey)|按权重随机从map中产生一个数据并返回数据和对应 key + + + +
+ + +*** +## 详情信息 +#### func Dice() int + +> 掷骰子 +> - 常规掷骰子将返回 1-6 的随机数 + +*** +#### func DiceN(n int) int + +> 掷骰子 +> - 与 Dice 不同的是,将返回 1-N 的随机数 + +*** +#### func NetIP() net.IP + +> 返回一个随机的IP地址 + +*** +#### func Port() int + +> 返回一个随机的端口号 + +*** +#### func IPv4() string + +> 返回一个随机产生的IPv4地址。 + +*** +#### func IPv4Port() string + +> 返回一个随机产生的IPv4地址和端口。 + +*** +#### func Int64(min int64, max int64) int64 + +> 返回一个介于min和max之间的int64类型的随机数。 + +*** +#### func Int(min int, max int) int + +> 返回一个介于min和max之间的的int类型的随机数。 + +*** +#### func Duration(min int64, max int64) time.Duration + +> 返回一个介于min和max之间的的Duration类型的随机数。 + +*** +#### func Float64() float64 + +> 返回一个0~1的浮点数 + +*** +#### func Float32() float32 + +> 返回一个0~1的浮点数 + +*** +#### func IntN(n int) int + +> 返回一个0~n的整数 + +*** +#### func Bool() bool + +> 返回一个随机的布尔值 + +*** +#### func ProbabilitySlice(getProbabilityHandle func (data T) float64, data ...T) (hit T, miss bool) + +> 按概率随机从切片中产生一个数据并返回命中的对象及是否未命中 +> - 当总概率小于 1 将会发生未命中的情况 + +
+查看 / 收起单元测试 + + +```go + +func TestProbabilitySlice(t *testing.T) { + var awards = []int{1, 2, 3, 4, 5, 6, 7} + var probability = []float64{0.1, 2, 0.1, 0.1, 0.1, 0.1, 0.1} + for i := 0; i < 50; i++ { + t.Log(random.ProbabilitySlice(func(data int) float64 { + return probability[data-1] + }, awards...)) + } +} + +``` + + +
+ + +*** +#### func ProbabilitySliceIndex(getProbabilityHandle func (data T) float64, data ...T) (hit T, index int, miss bool) + +> 按概率随机从切片中产生一个数据并返回命中的对象及对象索引以及是否未命中 +> - 当总概率小于 1 将会发生未命中的情况 + +*** +#### func Probability(p int, full ...int) bool + +> 输入一个概率,返回是否命中 +> - 当 full 不为空时,将以 full 为基数,p 为分子,计算命中概率 + +*** +#### func ProbabilityChooseOne(ps ...int) int + +> 输入一组概率,返回命中的索引 + +*** +#### func RefreshSeed(seed ...int64) + + +*** +#### func ChineseName() string + +> 返回一个随机组成的中文姓名。 + +*** +#### func EnglishName() string + +> 返回一个随机组成的英文姓名。 + +*** +#### func Name() string + +> 返回一个随机组成的中文或英文姓名 +> - 以1/2的概率决定生产的是中文还是英文姓名。 + +*** +#### func NumberString(min int, max int) string + +> 返回一个介于min和max之间的string类型的随机数。 + +*** +#### func NumberStringRepair(min int, max int) string + +> 返回一个介于min和max之间的string类型的随机数 +> - 通过Int64生成一个随机数,当结果的字符串长度小于max的字符串长度的情况下,使用0在开头补齐。 + +*** +#### func HostName() string + +> 返回一个随机产生的hostname。 + +*** +#### func WeightSlice(getWeightHandle func (data T) int64, data ...T) T + +> 按权重随机从切片中产生一个数据并返回 + +*** +#### func WeightSliceIndex(getWeightHandle func (data T) int64, data ...T) (item T, index int) + +> 按权重随机从切片中产生一个数据并返回数据和对应索引 + +*** +#### func WeightMap(getWeightHandle func (data T) int64, data map[K]T) T + +> 按权重随机从map中产生一个数据并返回 + +*** +#### func WeightMapKey(getWeightHandle func (data T) int64, data map[K]T) (item T, key K) + +> 按权重随机从map中产生一个数据并返回数据和对应 key + +*** diff --git a/utils/reflects/README.md b/utils/reflects/README.md new file mode 100644 index 0000000..bf685e0 --- /dev/null +++ b/utils/reflects/README.md @@ -0,0 +1,74 @@ +# Reflects + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[WrapperFunc](#WrapperFunc)|包装函数 +|[WrapperFuncBefore2After](#WrapperFuncBefore2After)|包装函数,前置函数执行前,后置函数执行后 +|[WrapperFuncBefore](#WrapperFuncBefore)|包装函数,前置函数执行前 +|[WrapperFuncAfter](#WrapperFuncAfter)|包装函数,后置函数执行后 +|[GetPtrUnExportFiled](#GetPtrUnExportFiled)|获取指针类型的未导出字段 +|[SetPtrUnExportFiled](#SetPtrUnExportFiled)|设置指针类型的未导出字段 +|[Copy](#Copy)|拷贝 +|[GetPointer](#GetPointer)|获取指针 + + + +
+ + +*** +## 详情信息 +#### func WrapperFunc(f any, wrapper func (call func ( []reflect.Value) []reflect.Value) func (args []reflect.Value) []reflect.Value) (wf Func, err error) + +> 包装函数 + +*** +#### func WrapperFuncBefore2After(f Func, before func (), after func ()) (wf Func, err error) + +> 包装函数,前置函数执行前,后置函数执行后 + +*** +#### func WrapperFuncBefore(f Func, before func ()) (wf Func, err error) + +> 包装函数,前置函数执行前 + +*** +#### func WrapperFuncAfter(f Func, after func ()) (wf Func, err error) + +> 包装函数,后置函数执行后 + +*** +#### func GetPtrUnExportFiled(s reflect.Value, filedIndex int) reflect.Value + +> 获取指针类型的未导出字段 + +*** +#### func SetPtrUnExportFiled(s reflect.Value, filedIndex int, val reflect.Value) + +> 设置指针类型的未导出字段 + +*** +#### func Copy(s reflect.Value) reflect.Value + +> 拷贝 + +*** +#### func GetPointer(src T) reflect.Value + +> 获取指针 + +*** diff --git a/utils/runtimes/README.md b/utils/runtimes/README.md new file mode 100644 index 0000000..63c3494 --- /dev/null +++ b/utils/runtimes/README.md @@ -0,0 +1,56 @@ +# Runtimes + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[GetWorkingDir](#GetWorkingDir)|获取工作目录绝对路径 +|[GetTempDir](#GetTempDir)|获取系统临时目录 +|[GetExecutablePathByBuild](#GetExecutablePathByBuild)|获取当前执行文件绝对路径(go build) +|[GetExecutablePathByCaller](#GetExecutablePathByCaller)|获取当前执行文件绝对路径(go run) +|[CurrentRunningFuncName](#CurrentRunningFuncName)|获取正在运行的函数名 + + + +
+ + +*** +## 详情信息 +#### func GetWorkingDir() string + +> 获取工作目录绝对路径 + +*** +#### func GetTempDir() string + +> 获取系统临时目录 + +*** +#### func GetExecutablePathByBuild() string + +> 获取当前执行文件绝对路径(go build) + +*** +#### func GetExecutablePathByCaller() string + +> 获取当前执行文件绝对路径(go run) + +*** +#### func CurrentRunningFuncName(skip ...int) string + +> 获取正在运行的函数名 + +*** diff --git a/utils/sole/README.md b/utils/sole/README.md new file mode 100644 index 0000000..b02da32 --- /dev/null +++ b/utils/sole/README.md @@ -0,0 +1,140 @@ +# Sole + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[RegNameSpace](#RegNameSpace)|注册特定命名空间的唯一标识符 +|[UnRegNameSpace](#UnRegNameSpace)|解除注销特定命名空间的唯一标识符 +|[Get](#Get)|获取全局唯一标识符 +|[Reset](#Reset)|重置全局唯一标识符 +|[GetWith](#GetWith)|获取特定命名空间的唯一标识符 +|[ResetWith](#ResetWith)|重置特定命名空间的唯一标识符 +|[NewOnce](#NewOnce)|创建一个用于数据取值去重的结构实例 +|[SonyflakeIDE](#SonyflakeIDE)|获取一个雪花id +|[SonyflakeID](#SonyflakeID)|获取一个雪花id +|[SonyflakeSetting](#SonyflakeSetting)|配置雪花id生成策略 +|[AutoIncrementUint32](#AutoIncrementUint32)|获取一个自增的 uint32 值 +|[AutoIncrementUint64](#AutoIncrementUint64)|获取一个自增的 uint64 值 +|[AutoIncrementInt32](#AutoIncrementInt32)|获取一个自增的 int32 值 +|[AutoIncrementInt64](#AutoIncrementInt64)|获取一个自增的 int64 值 +|[AutoIncrementInt](#AutoIncrementInt)|获取一个自增的 int 值 +|[AutoIncrementString](#AutoIncrementString)|获取一个自增的字符串 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[Once](#once)|用于数据取值去重的结构体 + +
+ + +*** +## 详情信息 +#### func RegNameSpace(name any) + +> 注册特定命名空间的唯一标识符 + +*** +#### func UnRegNameSpace(name any) + +> 解除注销特定命名空间的唯一标识符 + +*** +#### func Get() int64 + +> 获取全局唯一标识符 + +*** +#### func Reset() + +> 重置全局唯一标识符 + +*** +#### func GetWith(name any) int64 + +> 获取特定命名空间的唯一标识符 + +*** +#### func ResetWith(name any) + +> 重置特定命名空间的唯一标识符 + +*** +#### func NewOnce() *Once[V] + +> 创建一个用于数据取值去重的结构实例 + +*** +#### func SonyflakeIDE() int64, error + +> 获取一个雪花id + +*** +#### func SonyflakeID() int64 + +> 获取一个雪花id + +*** +#### func SonyflakeSetting(settings sonyflake.Settings) + +> 配置雪花id生成策略 + +*** +#### func AutoIncrementUint32() uint32 + +> 获取一个自增的 uint32 值 + +*** +#### func AutoIncrementUint64() uint64 + +> 获取一个自增的 uint64 值 + +*** +#### func AutoIncrementInt32() int32 + +> 获取一个自增的 int32 值 + +*** +#### func AutoIncrementInt64() int64 + +> 获取一个自增的 int64 值 + +*** +#### func AutoIncrementInt() int + +> 获取一个自增的 int 值 + +*** +#### func AutoIncrementString() string + +> 获取一个自增的字符串 + +*** +### Once `STRUCT` +用于数据取值去重的结构体 +```go +type Once[V any] struct { + r map[any]struct{} +} +``` +#### func (*Once) Get(key any, value V, defaultValue V) V +> 获取一个值,当该值已经被获取过的时候,返回 defaultValue,否则返回 value +*** +#### func (*Once) Reset(key ...any) +> 当 key 数量大于 0 时,将会重置对应 key 的记录,否则重置所有记录 +*** diff --git a/utils/sorts/README.md b/utils/sorts/README.md new file mode 100644 index 0000000..6375347 --- /dev/null +++ b/utils/sorts/README.md @@ -0,0 +1,125 @@ +# Sorts + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[Topological](#Topological)|拓扑排序是一种对有向图进行排序的算法,它可以用来解决一些依赖关系的问题,比如计算字段的依赖关系。拓扑排序会将存在依赖关系的元素进行排序,使得依赖关系的元素总是排在被依赖的元素之前。 + + + +
+ + +*** +## 详情信息 +#### func Topological(slice S, queryIndexHandler func (item V) Index, queryDependsHandler func (item V) []Index) S, error + +> 拓扑排序是一种对有向图进行排序的算法,它可以用来解决一些依赖关系的问题,比如计算字段的依赖关系。拓扑排序会将存在依赖关系的元素进行排序,使得依赖关系的元素总是排在被依赖的元素之前。 +> - slice: 需要排序的切片 +> - queryIndexHandler: 用于查询切片中每个元素的索引 +> - queryDependsHandler: 用于查询切片中每个元素的依赖关系,返回的是一个索引切片,如果没有依赖关系,那么返回空切片 +> +> 该函数在存在循环依赖的情况下将会返回 ErrCircularDependencyDetected 错误 + +示例代码: +```go + +func ExampleTopological() { + type Item struct { + ID int + Depends []int + } + var items = []Item{{ID: 2, Depends: []int{4}}, {ID: 1, Depends: []int{2, 3}}, {ID: 3, Depends: []int{4}}, {ID: 4, Depends: []int{5}}, {ID: 5, Depends: []int{}}} + var sorted, err = sorts.Topological(items, func(item Item) int { + return item.ID + }, func(item Item) []int { + return item.Depends + }) + if err != nil { + return + } + for _, item := range sorted { + fmt.Println(item.ID, "|", item.Depends) + } +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestTopological(t *testing.T) { + type Item struct { + ID int + Depends []int + } + var items = []Item{{ID: 2, Depends: []int{4}}, {ID: 1, Depends: []int{2, 3}}, {ID: 3, Depends: []int{4}}, {ID: 4, Depends: []int{5}}, {ID: 5, Depends: []int{}}} + var sorted, err = sorts.Topological(items, func(item Item) int { + return item.ID + }, func(item Item) []int { + return item.Depends + }) + if err != nil { + t.Error(err) + return + } + for _, item := range sorted { + t.Log(item.ID, "|", item.Depends) + } +} + +``` + + +
+ + +
+查看 / 收起基准测试 + + +```go + +func BenchmarkTopological(b *testing.B) { + type Item struct { + ID int + Depends []int + } + var items = []Item{{ID: 2, Depends: []int{4}}, {ID: 1, Depends: []int{2, 3}}, {ID: 3, Depends: []int{4}}, {ID: 4, Depends: []int{5}}, {ID: 5, Depends: []int{}}} + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := Topological(items, func(item Item) int { + return item.ID + }, func(item Item) []int { + return item.Depends + }) + if err != nil { + b.Error(err) + return + } + } +} + +``` + + +
+ + +*** diff --git a/utils/str/README.md b/utils/str/README.md new file mode 100644 index 0000000..04e35d0 --- /dev/null +++ b/utils/str/README.md @@ -0,0 +1,176 @@ +# Str + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[RangeLine](#RangeLine)|对传入的eachString进行按行切片后再进行遍历 +|[SplitTrimSpace](#SplitTrimSpace)|按照空格分割字符串并去除空格 +|[FirstUpper](#FirstUpper)|首字母大写 +|[FirstLower](#FirstLower)|首字母小写 +|[FirstUpperBytes](#FirstUpperBytes)|首字母大写 +|[FirstLowerBytes](#FirstLowerBytes)|首字母小写 +|[IsEmpty](#IsEmpty)|判断字符串是否为空 +|[IsEmptyBytes](#IsEmptyBytes)|判断字符串是否为空 +|[IsNotEmpty](#IsNotEmpty)|判断字符串是否不为空 +|[IsNotEmptyBytes](#IsNotEmptyBytes)|判断字符串是否不为空 +|[SnakeString](#SnakeString)|蛇形字符串 +|[SnakeStringBytes](#SnakeStringBytes)|蛇形字符串 +|[CamelString](#CamelString)|驼峰字符串 +|[CamelStringBytes](#CamelStringBytes)|驼峰字符串 +|[SortJoin](#SortJoin)|将多个字符串排序后拼接 +|[HideSensitivity](#HideSensitivity)|返回防敏感化后的字符串 +|[ThousandsSeparator](#ThousandsSeparator)|返回将str进行千位分隔符处理后的字符串。 +|[KV](#KV)|返回str经过转换后形成的key、value +|[FormatSpeedyInt](#FormatSpeedyInt)|返回numberStr经过格式化后去除空格和“,”分隔符的结果 +|[FormatSpeedyInt64](#FormatSpeedyInt64)|返回numberStr经过格式化后去除空格和“,”分隔符的结果 +|[FormatSpeedyFloat32](#FormatSpeedyFloat32)|返回numberStr经过格式化后去除空格和“,”分隔符的结果 +|[FormatSpeedyFloat64](#FormatSpeedyFloat64)|返回numberStr经过格式化后去除空格和“,”分隔符的结果 + + + +
+ + +*** +## 详情信息 +#### func RangeLine(eachString string, eachFunc func (index int, line string) error) error + +> 对传入的eachString进行按行切片后再进行遍历 +> - 该函数会预先对“\r\n”进行处理替换为“\n”。 +> - 在遍历到每一行的时候会将结果index和line作为入参传入eachFunc中进行调用。 +> - index表示了当前行的行号(由0开始),line表示了当前行的内容。 + +*** +#### func SplitTrimSpace(str string, sep string) []string + +> 按照空格分割字符串并去除空格 + +*** +#### func FirstUpper(str string) string + +> 首字母大写 + +*** +#### func FirstLower(str string) string + +> 首字母小写 + +*** +#### func FirstUpperBytes(str []byte) []byte + +> 首字母大写 + +*** +#### func FirstLowerBytes(str []byte) []byte + +> 首字母小写 + +*** +#### func IsEmpty(str string) bool + +> 判断字符串是否为空 + +*** +#### func IsEmptyBytes(str []byte) bool + +> 判断字符串是否为空 + +*** +#### func IsNotEmpty(str string) bool + +> 判断字符串是否不为空 + +*** +#### func IsNotEmptyBytes(str []byte) bool + +> 判断字符串是否不为空 + +*** +#### func SnakeString(str string) string + +> 蛇形字符串 + +*** +#### func SnakeStringBytes(str []byte) []byte + +> 蛇形字符串 + +*** +#### func CamelString(str string) string + +> 驼峰字符串 + +*** +#### func CamelStringBytes(str []byte) []byte + +> 驼峰字符串 + +*** +#### func SortJoin(delimiter string, s ...string) string + +> 将多个字符串排序后拼接 + +*** +#### func HideSensitivity(str string) (result string) + +> 返回防敏感化后的字符串 +> - 隐藏身份证、邮箱、手机号等敏感信息用*号替代 + +*** +#### func ThousandsSeparator(str string) string + +> 返回将str进行千位分隔符处理后的字符串。 + +*** +#### func KV(str string, tag ...string) string, string + +> 返回str经过转换后形成的key、value +> - 这里tag表示使用什么字符串来区分key和value的分隔符。 +> - 默认情况即不传入tag的情况下分隔符为“=”。 + +*** +#### func FormatSpeedyInt(numberStr string) int, error + +> 返回numberStr经过格式化后去除空格和“,”分隔符的结果 +> - 当字符串为“123,456,789”的时候,返回结果为“123456789”。 +> - 当字符串为“123 456 789”的时候,返回结果为“123456789”。 +> - 当字符串为“1 23, 45 6, 789”的时候,返回结果为“123456789”。 + +*** +#### func FormatSpeedyInt64(numberStr string) int64, error + +> 返回numberStr经过格式化后去除空格和“,”分隔符的结果 +> - 当字符串为“123,456,789”的时候,返回结果为“123456789”。 +> - 当字符串为“123 456 789”的时候,返回结果为“123456789”。 +> - 当字符串为“1 23, 45 6, 789”的时候,返回结果为“123456789”。 + +*** +#### func FormatSpeedyFloat32(numberStr string) float64, error + +> 返回numberStr经过格式化后去除空格和“,”分隔符的结果 +> - 当字符串为“123,456,789.123”的时候,返回结果为“123456789.123”。 +> - 当字符串为“123 456 789.123”的时候,返回结果为“123456789.123”。 +> - 当字符串为“1 23, 45 6, 789.123”的时候,返回结果为“123456789.123”。 + +*** +#### func FormatSpeedyFloat64(numberStr string) float64, error + +> 返回numberStr经过格式化后去除空格和“,”分隔符的结果 +> - 当字符串为“123,456,789.123”的时候,返回结果为“123456789.123”。 +> - 当字符串为“123 456 789.123”的时候,返回结果为“123456789.123”。 +> - 当字符串为“1 23, 45 6, 789.123”的时候,返回结果为“123456789.123”。 + +*** diff --git a/utils/super/README.md b/utils/super/README.md new file mode 100644 index 0000000..b3561fb --- /dev/null +++ b/utils/super/README.md @@ -0,0 +1,986 @@ +# Super + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[NewBitSet](#NewBitSet)|通过指定的 Bit 位创建一个 BitSet +|[TryWriteChannel](#TryWriteChannel)|尝试写入 channel,如果 channel 无法写入则忽略,返回是否写入成功 +|[TryWriteChannelByHandler](#TryWriteChannelByHandler)|尝试写入 channel,如果 channel 无法写入则执行 handler +|[RegError](#RegError)|通过错误码注册错误,返回错误的引用 +|[RegErrorRef](#RegErrorRef)|通过错误码注册错误,返回错误的引用 +|[GetError](#GetError)|通过错误引用获取错误码和真实错误信息,如果错误不存在则返回 0,如果错误引用不存在则返回原本的错误 +|[RecoverTransform](#RecoverTransform)|recover 错误转换 +|[Handle](#Handle)|执行 f 函数,如果 f 为 nil,则不执行 +|[HandleErr](#HandleErr)|执行 f 函数,如果 f 为 nil,则不执行 +|[HandleV](#HandleV)|执行 f 函数,如果 f 为 nil,则不执行 +|[GoFormat](#GoFormat)|go 代码格式化 +|[If](#If)|暂无描述... +|[MarshalJSON](#MarshalJSON)|将对象转换为 json +|[MarshalJSONE](#MarshalJSONE)|将对象转换为 json +|[UnmarshalJSON](#UnmarshalJSON)|将 json 转换为对象 +|[MarshalIndentJSON](#MarshalIndentJSON)|将对象转换为 json +|[MarshalToTargetWithJSON](#MarshalToTargetWithJSON)|将对象转换为目标对象 +|[StartLossCounter](#StartLossCounter)|开始损耗计数 +|[Match](#Match)|匹配 +|[IsNumber](#IsNumber)|判断是否为数字 +|[NumberToRome](#NumberToRome)|将数字转换为罗马数字 +|[StringToInt](#StringToInt)|字符串转换为整数 +|[StringToFloat64](#StringToFloat64)|字符串转换为 float64 +|[StringToBool](#StringToBool)|字符串转换为 bool +|[StringToUint64](#StringToUint64)|字符串转换为 uint64 +|[StringToUint](#StringToUint)|字符串转换为 uint +|[StringToFloat32](#StringToFloat32)|字符串转换为 float32 +|[StringToInt64](#StringToInt64)|字符串转换为 int64 +|[StringToUint32](#StringToUint32)|字符串转换为 uint32 +|[StringToInt32](#StringToInt32)|字符串转换为 int32 +|[StringToUint16](#StringToUint16)|字符串转换为 uint16 +|[StringToInt16](#StringToInt16)|字符串转换为 int16 +|[StringToUint8](#StringToUint8)|字符串转换为 uint8 +|[StringToInt8](#StringToInt8)|字符串转换为 int8 +|[StringToByte](#StringToByte)|字符串转换为 byte +|[StringToRune](#StringToRune)|字符串转换为 rune +|[IntToString](#IntToString)|整数转换为字符串 +|[Float64ToString](#Float64ToString)|float64 转换为字符串 +|[BoolToString](#BoolToString)|bool 转换为字符串 +|[Uint64ToString](#Uint64ToString)|uint64 转换为字符串 +|[UintToString](#UintToString)|uint 转换为字符串 +|[Float32ToString](#Float32ToString)|float32 转换为字符串 +|[Int64ToString](#Int64ToString)|int64 转换为字符串 +|[Uint32ToString](#Uint32ToString)|uint32 转换为字符串 +|[Int32ToString](#Int32ToString)|int32 转换为字符串 +|[Uint16ToString](#Uint16ToString)|uint16 转换为字符串 +|[Int16ToString](#Int16ToString)|int16 转换为字符串 +|[Uint8ToString](#Uint8ToString)|uint8 转换为字符串 +|[Int8ToString](#Int8ToString)|int8 转换为字符串 +|[ByteToString](#ByteToString)|byte 转换为字符串 +|[RuneToString](#RuneToString)|rune 转换为字符串 +|[StringToSlice](#StringToSlice)|字符串转换为切片 +|[SliceToString](#SliceToString)|切片转换为字符串 +|[NewPermission](#NewPermission)|创建权限 +|[Retry](#Retry)|根据提供的 count 次数尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功或者达到 count 次数 +|[RetryByRule](#RetryByRule)|根据提供的规则尝试执行 f 函数,如果 f 函数返回错误,则根据 rule 的返回值进行重试 +|[RetryByExponentialBackoff](#RetryByExponentialBackoff)|根据指数退避算法尝试执行 f 函数 +|[ConditionalRetryByExponentialBackoff](#ConditionalRetryByExponentialBackoff)|该函数与 RetryByExponentialBackoff 类似,但是可以被中断 +|[RetryAsync](#RetryAsync)|与 Retry 类似,但是是异步执行 +|[RetryForever](#RetryForever)|根据提供的 interval 时间间隔尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功 +|[NewStackGo](#NewStackGo)|返回一个用于获取上一个协程调用的堆栈信息的收集器 +|[LaunchTime](#LaunchTime)|获取程序启动时间 +|[Hostname](#Hostname)|获取主机名 +|[PID](#PID)|获取进程 PID +|[StringToBytes](#StringToBytes)|以零拷贝的方式将字符串转换为字节切片 +|[BytesToString](#BytesToString)|以零拷贝的方式将字节切片转换为字符串 +|[Convert](#Convert)|以零拷贝的方式将一个对象转换为另一个对象 +|[Verify](#Verify)|对特定表达式进行校验,当表达式不成立时,将执行 handle +|[OldVersion](#OldVersion)|检查 version2 对于 version1 来说是不是旧版本 +|[CompareVersion](#CompareVersion)|返回一个整数,用于表示两个版本号的比较结果: + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[BitSet](#bitset)|是一个可以动态增长的比特位集合 +|`STRUCT`|[LossCounter](#losscounter)|暂无描述... +|`STRUCT`|[Matcher](#matcher)|匹配器 +|`STRUCT`|[Permission](#permission)|暂无描述... +|`STRUCT`|[StackGo](#stackgo)|用于获取上一个协程调用的堆栈信息 +|`STRUCT`|[VerifyHandle](#verifyhandle)|校验句柄 + +
+ + +*** +## 详情信息 +#### func NewBitSet(bits ...Bit) *BitSet[Bit] + +> 通过指定的 Bit 位创建一个 BitSet + +*** +#### func TryWriteChannel(ch chan T, data T) bool + +> 尝试写入 channel,如果 channel 无法写入则忽略,返回是否写入成功 +> - 无法写入的情况包括:channel 已满、channel 已关闭 + +*** +#### func TryWriteChannelByHandler(ch chan T, data T, handler func ()) + +> 尝试写入 channel,如果 channel 无法写入则执行 handler +> - 无法写入的情况包括:channel 已满、channel 已关闭 + +*** +#### func RegError(code int, message string) error + +> 通过错误码注册错误,返回错误的引用 + +*** +#### func RegErrorRef(code int, message string, ref error) error + +> 通过错误码注册错误,返回错误的引用 +> - 引用将会被重定向到注册的错误信息 + +*** +#### func GetError(err error) int, error + +> 通过错误引用获取错误码和真实错误信息,如果错误不存在则返回 0,如果错误引用不存在则返回原本的错误 + +
+查看 / 收起单元测试 + + +```go + +func TestGetError(t *testing.T) { + var ErrNotFound = errors.New("not found") + var _ = super.RegErrorRef(100, "test error", ErrNotFound) + t.Log(super.GetError(ErrNotFound)) +} + +``` + + +
+ + +*** +#### func RecoverTransform(a any) error + +> recover 错误转换 + +示例代码: +```go + +func ExampleRecoverTransform() { + defer func() { + if err := super.RecoverTransform(recover()); err != nil { + fmt.Println(err) + } + }() + panic("test") +} + +``` + +*** +#### func Handle(f func ()) + +> 执行 f 函数,如果 f 为 nil,则不执行 + +*** +#### func HandleErr(f func () error) error + +> 执行 f 函数,如果 f 为 nil,则不执行 + +*** +#### func HandleV(v V, f func (v V)) + +> 执行 f 函数,如果 f 为 nil,则不执行 + +*** +#### func GoFormat(filePath string) + +> go 代码格式化 + +*** +#### func If(expression bool, t T, f T) T + + +*** +#### func MarshalJSON(v interface {}) []byte + +> 将对象转换为 json +> - 当转换失败时,将返回 json 格式的空对象 + +*** +#### func MarshalJSONE(v interface {}) []byte, error + +> 将对象转换为 json +> - 当转换失败时,将返回错误信息 + +*** +#### func UnmarshalJSON(data []byte, v interface {}) error + +> 将 json 转换为对象 + +*** +#### func MarshalIndentJSON(v interface {}, prefix string, indent string) []byte + +> 将对象转换为 json + +*** +#### func MarshalToTargetWithJSON(src interface {}, dest interface {}) error + +> 将对象转换为目标对象 + +*** +#### func StartLossCounter() *LossCounter + +> 开始损耗计数 + +*** +#### func Match(value Value) *Matcher[Value, Result] + +> 匹配 + +
+查看 / 收起单元测试 + + +```go + +func TestMatch(t *testing.T) { + Convey("TestMatch", t, func() { + So(super.Match[int, string](1).Case(1, "a").Case(2, "b").Default("c"), ShouldEqual, "a") + So(super.Match[int, string](2).Case(1, "a").Case(2, "b").Default("c"), ShouldEqual, "b") + So(super.Match[int, string](3).Case(1, "a").Case(2, "b").Default("c"), ShouldEqual, "c") + }) +} + +``` + + +
+ + +*** +#### func IsNumber(v any) bool + +> 判断是否为数字 + +*** +#### func NumberToRome(num int) string + +> 将数字转换为罗马数字 + +
+查看 / 收起单元测试 + + +```go + +func TestNumberToRome(t *testing.T) { + tests := []struct { + input int + output string + }{{input: 1, output: "I"}, {input: 5, output: "V"}, {input: 10, output: "X"}, {input: 50, output: "L"}, {input: 100, output: "C"}, {input: 500, output: "D"}, {input: 1000, output: "M"}} + for _, test := range tests { + result := super.NumberToRome(test.input) + if result != test.output { + t.Errorf("NumberToRome(%d) = %s; want %s", test.input, result, test.output) + } + } +} + +``` + + +
+ + +*** +#### func StringToInt(value string) int + +> 字符串转换为整数 + +*** +#### func StringToFloat64(value string) float64 + +> 字符串转换为 float64 + +*** +#### func StringToBool(value string) bool + +> 字符串转换为 bool + +*** +#### func StringToUint64(value string) uint64 + +> 字符串转换为 uint64 + +*** +#### func StringToUint(value string) uint + +> 字符串转换为 uint + +*** +#### func StringToFloat32(value string) float32 + +> 字符串转换为 float32 + +*** +#### func StringToInt64(value string) int64 + +> 字符串转换为 int64 + +*** +#### func StringToUint32(value string) uint32 + +> 字符串转换为 uint32 + +*** +#### func StringToInt32(value string) int32 + +> 字符串转换为 int32 + +*** +#### func StringToUint16(value string) uint16 + +> 字符串转换为 uint16 + +*** +#### func StringToInt16(value string) int16 + +> 字符串转换为 int16 + +*** +#### func StringToUint8(value string) uint8 + +> 字符串转换为 uint8 + +*** +#### func StringToInt8(value string) int8 + +> 字符串转换为 int8 + +*** +#### func StringToByte(value string) byte + +> 字符串转换为 byte + +*** +#### func StringToRune(value string) rune + +> 字符串转换为 rune + +*** +#### func IntToString(value int) string + +> 整数转换为字符串 + +*** +#### func Float64ToString(value float64) string + +> float64 转换为字符串 + +*** +#### func BoolToString(value bool) string + +> bool 转换为字符串 + +*** +#### func Uint64ToString(value uint64) string + +> uint64 转换为字符串 + +*** +#### func UintToString(value uint) string + +> uint 转换为字符串 + +*** +#### func Float32ToString(value float32) string + +> float32 转换为字符串 + +*** +#### func Int64ToString(value int64) string + +> int64 转换为字符串 + +*** +#### func Uint32ToString(value uint32) string + +> uint32 转换为字符串 + +*** +#### func Int32ToString(value int32) string + +> int32 转换为字符串 + +*** +#### func Uint16ToString(value uint16) string + +> uint16 转换为字符串 + +*** +#### func Int16ToString(value int16) string + +> int16 转换为字符串 + +*** +#### func Uint8ToString(value uint8) string + +> uint8 转换为字符串 + +*** +#### func Int8ToString(value int8) string + +> int8 转换为字符串 + +*** +#### func ByteToString(value byte) string + +> byte 转换为字符串 + +*** +#### func RuneToString(value rune) string + +> rune 转换为字符串 + +*** +#### func StringToSlice(value string) []string + +> 字符串转换为切片 + +*** +#### func SliceToString(value []string) string + +> 切片转换为字符串 + +*** +#### func NewPermission() *Permission[Code, EntityID] + +> 创建权限 + +
+查看 / 收起单元测试 + + +```go + +func TestNewPermission(t *testing.T) { + const ( + Read = 1 << iota + Write + Execute + ) + p := super.NewPermission[int, int]() + p.AddPermission(1, Read, Write) + t.Log(p.HasPermission(1, Read)) + t.Log(p.HasPermission(1, Write)) + p.SetPermission(2, Read|Write) + t.Log(p.HasPermission(2, Read)) + t.Log(p.HasPermission(2, Execute)) + p.SetPermission(2, Execute) + t.Log(p.HasPermission(2, Execute)) + t.Log(p.HasPermission(2, Read)) + t.Log(p.HasPermission(2, Write)) + p.RemovePermission(2, Execute) + t.Log(p.HasPermission(2, Execute)) +} + +``` + + +
+ + +*** +#### func Retry(count int, interval time.Duration, f func () error) error + +> 根据提供的 count 次数尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功或者达到 count 次数 + +*** +#### func RetryByRule(f func () error, rule func (count int) time.Duration) error + +> 根据提供的规则尝试执行 f 函数,如果 f 函数返回错误,则根据 rule 的返回值进行重试 +> - rule 将包含一个入参,表示第几次重试,返回值表示下一次重试的时间间隔,当返回值为 0 时,表示不再重试 +> - rule 的 count 将在 f 首次失败后变为 1,因此 rule 的入参将从 1 开始 + +*** +#### func RetryByExponentialBackoff(f func () error, maxRetries int, baseDelay time.Duration, maxDelay time.Duration, multiplier float64, randomization float64, ignoreErrors ...error) error + +> 根据指数退避算法尝试执行 f 函数 +> - maxRetries:最大重试次数 +> - baseDelay:基础延迟 +> - maxDelay:最大延迟 +> - multiplier:延迟时间的乘数,通常为 2 +> - randomization:延迟时间的随机化因子,通常为 0.5 +> - ignoreErrors:忽略的错误,当 f 返回的错误在 ignoreErrors 中时,将不会进行重试 + +*** +#### func ConditionalRetryByExponentialBackoff(f func () error, cond func () bool, maxRetries int, baseDelay time.Duration, maxDelay time.Duration, multiplier float64, randomization float64, ignoreErrors ...error) error + +> 该函数与 RetryByExponentialBackoff 类似,但是可以被中断 +> - cond 为中断条件,当 cond 返回 false 时,将会中断重试 +> +> 该函数通常用于在重试过程中,需要中断重试的场景,例如: +> - 用户请求开始游戏,由于网络等情况,进入重试状态。此时用户再次发送开始游戏请求,此时需要中断之前的重试,避免重复进入游戏 + +*** +#### func RetryAsync(count int, interval time.Duration, f func () error, callback func (err error)) + +> 与 Retry 类似,但是是异步执行 +> - 传入的 callback 函数会在执行完毕后被调用,如果执行成功,则 err 为 nil,否则为错误信息 +> - 如果 callback 为 nil,则不会在执行完毕后被调用 + +*** +#### func RetryForever(interval time.Duration, f func () error) + +> 根据提供的 interval 时间间隔尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功 + +*** +#### func NewStackGo() *StackGo + +> 返回一个用于获取上一个协程调用的堆栈信息的收集器 + +*** +#### func LaunchTime() time.Time + +> 获取程序启动时间 + +*** +#### func Hostname() string + +> 获取主机名 + +*** +#### func PID() int + +> 获取进程 PID + +*** +#### func StringToBytes(s string) []byte + +> 以零拷贝的方式将字符串转换为字节切片 + +*** +#### func BytesToString(b []byte) string + +> 以零拷贝的方式将字节切片转换为字符串 + +*** +#### func Convert(src A) B + +> 以零拷贝的方式将一个对象转换为另一个对象 +> - 两个对象字段必须完全一致 +> - 该函数可以绕过私有字段的访问限制 + +
+查看 / 收起单元测试 + + +```go + +func TestConvert(t *testing.T) { + type B struct { + nocmp [0]func() + v atomic.Value + } + var a = atomic.NewString("hello") + var b = super.Convert[*atomic.String, *B](a) + fmt.Println(b.v.Load()) +} + +``` + + +
+ + +*** +#### func Verify(handle func ( V)) *VerifyHandle[V] + +> 对特定表达式进行校验,当表达式不成立时,将执行 handle + +示例代码: +```go + +func ExampleVerify() { + var getId = func() int { + return 1 + } + var n *super.VerifyHandle[int] + super.Verify(func(err error) { + fmt.Println(err) + }).Case(getId() == 1, errors.New("id can't be 1")).Do() + super.Verify(func(err error) { + fmt.Println(err) + }).PreCase(func() bool { + return n == nil + }, errors.New("n can't be nil"), func(verify *super.VerifyHandle[error]) bool { + return verify.Do() + }) +} + +``` + +*** +#### func OldVersion(version1 string, version2 string) bool + +> 检查 version2 对于 version1 来说是不是旧版本 + +示例代码: +```go + +func ExampleOldVersion() { + result := super.OldVersion("1.2.3", "1.2.2") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestOldVersion(t *testing.T) { + testCases := []struct { + version1 string + version2 string + want bool + }{{"1.2.3", "1.2.2", true}, {"1.2.1", "1.2.2", false}, {"1.2.3", "1.2.3", false}, {"v1.2.3", "v1.2.2", true}, {"v1.2.3", "v1.2.4", false}, {"v1.2.3", "1.2.3", false}, {"vxx2faf.d2ad5.dd3", "gga2faf.d2ad5.dd2", true}, {"awd2faf.d2ad4.dd3", "vsd2faf.d2ad5.dd3", false}, {"vxd2faf.d2ad5.dd3", "qdq2faf.d2ad5.dd3", false}, {"1.2.3", "vdafe2faf.d2ad5.dd3", false}, {"v1.2.3", "vdafe2faf.d2ad5.dd3", false}} + for _, tc := range testCases { + got := super.OldVersion(tc.version1, tc.version2) + if got != tc.want { + t.Errorf("OldVersion(%q, %q) = %v; want %v", tc.version1, tc.version2, got, tc.want) + } + } +} + +``` + + +
+ + +
+查看 / 收起基准测试 + + +```go + +func BenchmarkOldVersion(b *testing.B) { + for i := 0; i < b.N; i++ { + super.OldVersion("vfe2faf.d2ad5.dd3", "vda2faf.d2ad5.dd2") + } +} + +``` + + +
+ + +*** +#### func CompareVersion(version1 string, version2 string) int + +> 返回一个整数,用于表示两个版本号的比较结果: +> - 如果 version1 大于 version2,它将返回 1 +> - 如果 version1 小于 version2,它将返回 -1 +> - 如果 version1 和 version2 相等,它将返回 0 + +示例代码: +```go + +func ExampleCompareVersion() { + result := super.CompareVersion("1.2.3", "1.2.2") + fmt.Println(result) +} + +``` + +
+查看 / 收起单元测试 + + +```go + +func TestCompareVersion(t *testing.T) { + testCases := []struct { + version1 string + version2 string + want int + }{{"1.2.3", "1.2.2", 1}, {"1.2.1", "1.2.2", -1}, {"1.2.3", "1.2.3", 0}, {"v1.2.3", "v1.2.2", 1}, {"v1.2.3", "v1.2.4", -1}, {"v1.2.3", "1.2.3", 0}, {"vde2faf.d2ad5.dd3", "e2faf.d2ad5.dd2", 1}, {"vde2faf.d2ad4.dd3", "vde2faf.d2ad5.dd3", -1}, {"vfe2faf.d2ad5.dd3", "ve2faf.d2ad5.dd3", 0}, {"1.2.3", "vdafe2faf.d2ad5.dd3", -1}, {"v1.2.3", "vdafe2faf.d2ad5.dd3", -1}} + for _, tc := range testCases { + got := super.CompareVersion(tc.version1, tc.version2) + if got != tc.want { + t.Errorf("CompareVersion(%q, %q) = %v; want %v", tc.version1, tc.version2, got, tc.want) + } + } +} + +``` + + +
+ + +
+查看 / 收起基准测试 + + +```go + +func BenchmarkCompareVersion(b *testing.B) { + for i := 0; i < b.N; i++ { + super.CompareVersion("vfe2faf.d2ad5.dd3", "afe2faf.d2ad5.dd2") + } +} + +``` + + +
+ + +*** +### BitSet `STRUCT` +是一个可以动态增长的比特位集合 + - 默认情况下将使用 64 位无符号整数来表示比特位,当需要表示的比特位超过 64 位时,将自动增长 +```go +type BitSet[Bit generic.Integer] struct { + set []uint64 +} +``` +#### func (*BitSet) Set(bit Bit) *BitSet[Bit] +> 将指定的位 bit 设置为 1 +
+查看 / 收起单元测试 + + +```go + +func TestBitSet_Set(t *testing.T) { + bs := super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + bs.Set(11) + bs.Set(12) + bs.Set(13) + t.Log(bs) +} + +``` + + +
+ + +*** +#### func (*BitSet) Del(bit Bit) *BitSet[Bit] +> 将指定的位 bit 设置为 0 +
+查看 / 收起单元测试 + + +```go + +func TestBitSet_Del(t *testing.T) { + bs := super.NewBitSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + bs.Del(11) + bs.Del(12) + bs.Del(13) + bs.Del(10) + t.Log(bs) +} + +``` + + +
+ + +*** +#### func (*BitSet) Shrink() *BitSet[Bit] +> 将 BitSet 中的比特位集合缩小到最小 +> - 正常情况下当 BitSet 中的比特位超出 64 位时,将自动增长,当 BitSet 中的比特位数量减少时,可以使用该方法将 BitSet 中的比特位集合缩小到最小 +
+查看 / 收起单元测试 + + +```go + +func TestBitSet_Shrink(t *testing.T) { + bs := super.NewBitSet(63) + t.Log(bs.Cap()) + bs.Set(200) + t.Log(bs.Cap()) + bs.Del(200) + bs.Shrink() + t.Log(bs.Cap()) +} + +``` + + +
+ + +*** +#### func (*BitSet) Cap() int +> 返回当前 BitSet 中可以表示的最大比特位数量 +*** +#### func (*BitSet) Has(bit Bit) bool +> 检查指定的位 bit 是否被设置为 1 +*** +#### func (*BitSet) Clear() *BitSet[Bit] +> 清空所有的比特位 +*** +#### func (*BitSet) Len() int +> 返回当前 BitSet 中被设置的比特位数量 +*** +#### func (*BitSet) Bits() []Bit +> 返回当前 BitSet 中被设置的比特位 +*** +#### func (*BitSet) Reverse() *BitSet[Bit] +> 反转当前 BitSet 中的所有比特位 +*** +#### func (*BitSet) Not() *BitSet[Bit] +> 返回当前 BitSet 中所有比特位的反转 +*** +#### func (*BitSet) And(other *BitSet[Bit]) *BitSet[Bit] +> 将当前 BitSet 与另一个 BitSet 进行按位与运算 +*** +#### func (*BitSet) Or(other *BitSet[Bit]) *BitSet[Bit] +> 将当前 BitSet 与另一个 BitSet 进行按位或运算 +*** +#### func (*BitSet) Xor(other *BitSet[Bit]) *BitSet[Bit] +> 将当前 BitSet 与另一个 BitSet 进行按位异或运算 +*** +#### func (*BitSet) Sub(other *BitSet[Bit]) *BitSet[Bit] +> 将当前 BitSet 与另一个 BitSet 进行按位减运算 +*** +#### func (*BitSet) IsZero() bool +> 检查当前 BitSet 是否为空 +*** +#### func (*BitSet) Clone() *BitSet[Bit] +> 返回当前 BitSet 的副本 +*** +#### func (*BitSet) Equal(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否与另一个 BitSet 相等 +*** +#### func (*BitSet) Contains(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否包含另一个 BitSet +*** +#### func (*BitSet) ContainsAny(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否包含另一个 BitSet 中的任意比特位 +*** +#### func (*BitSet) ContainsAll(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否包含另一个 BitSet 中的所有比特位 +*** +#### func (*BitSet) Intersect(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否与另一个 BitSet 有交集 +*** +#### func (*BitSet) Union(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否与另一个 BitSet 有并集 +*** +#### func (*BitSet) Difference(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否与另一个 BitSet 有差集 +*** +#### func (*BitSet) SymmetricDifference(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否与另一个 BitSet 有对称差集 +*** +#### func (*BitSet) Subset(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否为另一个 BitSet 的子集 +*** +#### func (*BitSet) Superset(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否为另一个 BitSet 的超集 +*** +#### func (*BitSet) Complement(other *BitSet[Bit]) bool +> 检查当前 BitSet 是否为另一个 BitSet 的补集 +*** +#### func (*BitSet) Max() Bit +> 返回当前 BitSet 中最大的比特位 +*** +#### func (*BitSet) Min() Bit +> 返回当前 BitSet 中最小的比特位 +*** +#### func (*BitSet) String() string +> 返回当前 BitSet 的字符串表示 +*** +#### func (*BitSet) MarshalJSON() []byte, error +> 实现 json.Marshaler 接口 +*** +#### func (*BitSet) UnmarshalJSON(data []byte) error +> 实现 json.Unmarshaler 接口 +*** +### LossCounter `STRUCT` + +```go +type LossCounter struct { + curr time.Time + loss []time.Duration + lossKey []string +} +``` +#### func (*LossCounter) Record(name string) +> 记录一次损耗 +*** +#### func (*LossCounter) GetLoss(handler func (step int, name string, loss time.Duration)) +> 获取损耗 +*** +#### func (*LossCounter) String() string +*** +### Matcher `STRUCT` +匹配器 +```go +type Matcher[Value any, Result any] struct { + value Value + r Result + d bool +} +``` +### Permission `STRUCT` + +```go +type Permission[Code generic.Integer, EntityID comparable] struct { + permissions map[EntityID]Code + l sync.RWMutex +} +``` +### StackGo `STRUCT` +用于获取上一个协程调用的堆栈信息 + - 应当最先运行 Wait 函数,然后在其他协程中调用 Stack 函数或者 GiveUp 函数 + - 适用于跨协程同步通讯,例如单线程的消息处理统计耗时打印堆栈信息 +```go +type StackGo struct { + stack chan *struct{} + collect chan []byte +} +``` +#### func (*StackGo) Wait() +> 等待收集消息堆栈 +> - 在调用 Wait 函数后,当前协程将会被挂起,直到调用 Stack 或 GiveUp 函数 +*** +#### func (*StackGo) Stack() []byte +> 获取消息堆栈 +> - 在调用 Wait 函数后调用该函数,将会返回上一个协程的堆栈信息 +> - 在调用 GiveUp 函数后调用该函数,将会 panic +*** +#### func (*StackGo) GiveUp() +> 放弃收集消息堆栈 +> - 在调用 Wait 函数后调用该函数,将会放弃收集消息堆栈并且释放资源 +> - 在调用 GiveUp 函数后调用 Stack 函数,将会 panic +*** +### VerifyHandle `STRUCT` +校验句柄 +```go +type VerifyHandle[V any] struct { + handle func(V) + v V + hit bool +} +``` +#### func (*VerifyHandle) PreCase(expression func () bool, value V, caseHandle func (verify *VerifyHandle[V]) bool) bool +> 先决校验用例,当 expression 成立时,将跳过 caseHandle 的执行,直接执行 handle 并返回 false +> - 常用于对前置参数的空指针校验,例如当 a 为 nil 时,不执行 a.B(),而是直接返回 false +*** +#### func (*VerifyHandle) Case(expression bool, value V) *VerifyHandle[V] +> 校验用例,当 expression 成立时,将忽略后续 Case,并将在 Do 时执行 handle,返回 false +*** +#### func (*VerifyHandle) Do() bool +> 执行校验,当校验失败时,将执行 handle,并返回 false +*** diff --git a/utils/timer/README.md b/utils/timer/README.md new file mode 100644 index 0000000..63d2f22 --- /dev/null +++ b/utils/timer/README.md @@ -0,0 +1,218 @@ +# Timer + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[RegSystemNewDayEvent](#RegSystemNewDayEvent)|注册系统新的一天事件 +|[OnSystemNewDayEvent](#OnSystemNewDayEvent)|系统新的一天事件 +|[RegOffsetTimeNewDayEvent](#RegOffsetTimeNewDayEvent)|注册偏移时间新的一天事件 +|[OnOffsetTimeNewDayEvent](#OnOffsetTimeNewDayEvent)|偏移时间新的一天事件 +|[WithCaller](#WithCaller)|通过其他的 handle 执行 Caller +|[WithMark](#WithMark)|通过特定的标记创建定时器 +|[NewPool](#NewPool)|创建一个定时器池,当 tickerPoolSize 小于等于 0 时,将会引发 panic,可指定为 DefaultTickerPoolSize +|[SetPoolSize](#SetPoolSize)|设置标准池定时器池大小 +|[GetTicker](#GetTicker)|获取标准池中的一个定时器 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[SystemNewDayEventHandle](#systemnewdayeventhandle)|暂无描述... +|`STRUCT`|[Option](#option)|暂无描述... +|`STRUCT`|[Pool](#pool)|定时器池 +|`STRUCT`|[Scheduler](#scheduler)|调度器 +|`STRUCT`|[Ticker](#ticker)|定时器 + +
+ + +*** +## 详情信息 +#### func RegSystemNewDayEvent(ticker *Ticker, name string, trigger bool, handle SystemNewDayEventHandle) + +> 注册系统新的一天事件 +> - 建议全局注册一个事件后再另行拓展 +> - 将特定 name 的定时任务注册到 ticker 中,在系统时间到达每天的 00:00:00 时触发,如果 trigger 为 true,则立即触发一次 + +*** +#### func OnSystemNewDayEvent(name string) + +> 系统新的一天事件 + +*** +#### func RegOffsetTimeNewDayEvent(ticker *Ticker, name string, offset *offset.Time, trigger bool, handle OffsetTimeNewDayEventHandle) + +> 注册偏移时间新的一天事件 +> - 建议全局注册一个事件后再另行拓展 +> - 与 RegSystemNewDayEvent 类似,但是触发时间为 offset 时间到达每天的 00:00:00 + +*** +#### func OnOffsetTimeNewDayEvent(name string) + +> 偏移时间新的一天事件 + +*** +#### func WithCaller(handle func (name string, caller func ())) Option + +> 通过其他的 handle 执行 Caller + +*** +#### func WithMark(mark string) Option + +> 通过特定的标记创建定时器 + +*** +#### func NewPool(tickerPoolSize int) *Pool + +> 创建一个定时器池,当 tickerPoolSize 小于等于 0 时,将会引发 panic,可指定为 DefaultTickerPoolSize + +*** +#### func SetPoolSize(size int) + +> 设置标准池定时器池大小 +> - 默认值为 DefaultTickerPoolSize,当定时器池中的定时器不足时,会自动创建新的定时器,当定时器释放时,会将多余的定时器进行释放,否则将放入定时器池中 + +*** +#### func GetTicker(size int, options ...Option) *Ticker + +> 获取标准池中的一个定时器 + +*** +### SystemNewDayEventHandle `STRUCT` + +```go +type SystemNewDayEventHandle func() +``` +### Option `STRUCT` + +```go +type Option func(ticker *Ticker) +``` +### Pool `STRUCT` +定时器池 +```go +type Pool struct { + tickers []*Ticker + lock sync.Mutex + tickerPoolSize int + closed bool +} +``` +#### func (*Pool) ChangePoolSize(size int) error +> 改变定时器池大小 +> - 当传入的大小小于或等于 0 时,将会返回错误,并且不会发生任何改变 +*** +#### func (*Pool) GetTicker(size int, options ...Option) *Ticker +> 获取一个新的定时器 +*** +#### func (*Pool) Release() +> 释放定时器池的资源,释放后由其产生的 Ticker 在 Ticker.Release 后将不再回到池中,而是直接释放 +> - 虽然定时器池已被释放,但是依旧可以产出 Ticker +*** +### Scheduler `STRUCT` +调度器 +```go +type Scheduler struct { + name string + after time.Duration + interval time.Duration + total int + trigger int + kill bool + cbFunc reflect.Value + cbArgs []reflect.Value + timer *timingwheel.Timer + ticker *Ticker + lock sync.RWMutex + expr *cronexpr.Expression +} +``` +#### func (*Scheduler) Name() string +> 获取调度器名称 +*** +#### func (*Scheduler) Next(prev time.Time) time.Time +> 获取下一次执行的时间 +*** +#### func (*Scheduler) Caller() +> 可由外部发起调用的执行函数 +*** +### Ticker `STRUCT` +定时器 +```go +type Ticker struct { + timer *Pool + wheel *timingwheel.TimingWheel + timers map[string]*Scheduler + lock sync.RWMutex + handle func(name string, caller func()) + mark string +} +``` +#### func (*Ticker) Mark() string +> 获取定时器的标记 +> - 通常用于鉴别定时器来源 +*** +#### func (*Ticker) Release() +> 释放定时器,并将定时器重新放回 Pool 池中 +*** +#### func (*Ticker) StopTimer(name string) +> 停止特定名称的调度器 +*** +#### func (*Ticker) IsStopped(name string) bool +> 特定名称的调度器是否已停止 +*** +#### func (*Ticker) GetSchedulers() []string +> 获取所有调度器名称 +*** +#### func (*Ticker) Cron(name string, expression string, handleFunc interface {}, args ...interface {}) +> 通过 cron 表达式设置一个调度器,当 cron 表达式错误时,将会引发 panic +
+查看 / 收起单元测试 + + +```go + +func TestTicker_Cron(t *testing.T) { + ticker := timer.GetTicker(10) + ticker.After("1_sec", time.Second, func() { + t.Log(time.Now().Format(time.DateTime), "1_sec") + }) + ticker.Loop("1_sec_loop_3", 0, time.Second, 3, func() { + t.Log(time.Now().Format(time.DateTime), "1_sec_loop_3") + }) + ticker.Cron("5_sec_cron", "0/5 * * * * * ?", func() { + t.Log(time.Now().Format(time.DateTime), "5_sec_cron") + }) + time.Sleep(times.Week) +} + +``` + + +
+ + +*** +#### func (*Ticker) CronByInstantly(name string, expression string, handleFunc interface {}, args ...interface {}) +> 与 Cron 相同,但是会立即执行一次 +*** +#### func (*Ticker) After(name string, after time.Duration, handleFunc interface {}, args ...interface {}) +> 设置一个在特定时间后运行一次的调度器 +*** +#### func (*Ticker) Loop(name string, after time.Duration, interval time.Duration, times int, handleFunc interface {}, args ...interface {}) +> 设置一个在特定时间后反复运行的调度器 +*** diff --git a/utils/times/README.md b/utils/times/README.md new file mode 100644 index 0000000..ff82c67 --- /dev/null +++ b/utils/times/README.md @@ -0,0 +1,551 @@ +# Times + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[CalcNextSec](#CalcNextSec)|计算下一个N秒在多少秒之后 +|[CalcNextSecWithTime](#CalcNextSecWithTime)|计算下一个N秒在多少秒之后 +|[CalcNextTimeWithRefer](#CalcNextTimeWithRefer)|根据参考时间计算下一个整点时间 +|[IntervalFormatSet](#IntervalFormatSet)|针对 IntervalFormat 函数设置格式化内容 +|[IntervalFormat](#IntervalFormat)|返回指定时间戳之间的间隔 +|[GetMonthDays](#GetMonthDays)|获取一个时间当月共有多少天 +|[WeekDay](#WeekDay)|获取一个时间是星期几 +|[GetNextDayInterval](#GetNextDayInterval)|获取一个时间到下一天间隔多少秒 +|[GetToday](#GetToday)|获取一个时间的今天 +|[GetSecond](#GetSecond)|获取共有多少秒 +|[IsSameDay](#IsSameDay)|两个时间是否是同一天 +|[IsSameHour](#IsSameHour)|两个时间是否是同一小时 +|[GetMondayZero](#GetMondayZero)|获取本周一零点 +|[Date](#Date)|返回一个特定日期的时间 +|[DateWithHMS](#DateWithHMS)|返回一个精确到秒的时间 +|[GetDeltaDay](#GetDeltaDay)|获取两个时间需要加减的天数 +|[GetDeltaWeek](#GetDeltaWeek)|获取两个时间需要加减的周数 +|[GetHSMFromString](#GetHSMFromString)|从时间字符串中获取时分秒 +|[GetTimeFromString](#GetTimeFromString)|将时间字符串转化为时间 +|[GetDayZero](#GetDayZero)|获取 t 增加 day 天后的零点时间 +|[GetYesterday](#GetYesterday)|获取昨天 +|[GetDayLast](#GetDayLast)|获取某天的最后一刻 +|[GetYesterdayLast](#GetYesterdayLast)|获取昨天最后一刻 +|[GetMinuteStart](#GetMinuteStart)|获取一个时间的 0 秒 +|[GetMinuteEnd](#GetMinuteEnd)|获取一个时间的 59 秒 +|[GetHourStart](#GetHourStart)|获取一个时间的 0 分 0 秒 +|[GetHourEnd](#GetHourEnd)|获取一个时间的 59 分 59 秒 +|[GetMonthStart](#GetMonthStart)|获取一个时间的月初 +|[GetMonthEnd](#GetMonthEnd)|获取一个时间的月末 +|[GetYearStart](#GetYearStart)|获取一个时间的年初 +|[GetYearEnd](#GetYearEnd)|获取一个时间的年末 +|[NewStateLine](#NewStateLine)|创建一个从左向右由早到晚的状态时间线 +|[SetGlobalTimeOffset](#SetGlobalTimeOffset)|设置全局时间偏移量 +|[NowByNotOffset](#NowByNotOffset)|获取未偏移的当前时间 +|[GetGlobalTimeOffset](#GetGlobalTimeOffset)|获取全局时间偏移量 +|[ResetGlobalTimeOffset](#ResetGlobalTimeOffset)|重置全局时间偏移量 +|[NewPeriod](#NewPeriod)|创建一个时间段 +|[NewPeriodWindow](#NewPeriodWindow)|创建一个特定长度的时间窗口 +|[NewPeriodWindowWeek](#NewPeriodWindowWeek)|创建一周长度的时间窗口,从周一零点开始至周日 23:59:59 结束 +|[NewPeriodWithTimeArray](#NewPeriodWithTimeArray)|创建一个时间段 +|[NewPeriodWithDayZero](#NewPeriodWithDayZero)|创建一个时间段,从 t 开始,持续到 day 天后的 0 点 +|[NewPeriodWithDay](#NewPeriodWithDay)|创建一个时间段,从 t 开始,持续 day 天 +|[NewPeriodWithHour](#NewPeriodWithHour)|创建一个时间段,从 t 开始,持续 hour 小时 +|[NewPeriodWithMinute](#NewPeriodWithMinute)|创建一个时间段,从 t 开始,持续 minute 分钟 +|[NewPeriodWithSecond](#NewPeriodWithSecond)|创建一个时间段,从 t 开始,持续 second 秒 +|[NewPeriodWithMillisecond](#NewPeriodWithMillisecond)|创建一个时间段,从 t 开始,持续 millisecond 毫秒 +|[NewPeriodWithMicrosecond](#NewPeriodWithMicrosecond)|创建一个时间段,从 t 开始,持续 microsecond 微秒 +|[NewPeriodWithNanosecond](#NewPeriodWithNanosecond)|创建一个时间段,从 t 开始,持续 nanosecond 纳秒 + + +> 类型定义 + +|类型|名称|描述 +|:--|:--|:-- +|`STRUCT`|[StateLine](#stateline)|状态时间线 +|`STRUCT`|[Period](#period)|表示一个时间段 + +
+ + +*** +## 详情信息 +#### func CalcNextSec(sec int) int + +> 计算下一个N秒在多少秒之后 + +*** +#### func CalcNextSecWithTime(t time.Time, sec int) int + +> 计算下一个N秒在多少秒之后 + +示例代码: +```go + +func ExampleCalcNextSecWithTime() { + now := time.Date(2023, 9, 20, 0, 0, 3, 0, time.Local) + fmt.Println(times.CalcNextSecWithTime(now, 10)) +} + +``` + +*** +#### func CalcNextTimeWithRefer(now time.Time, refer time.Duration) time.Time + +> 根据参考时间计算下一个整点时间 +> - 假设当 now 为 14:15:16 , 参考时间为 10 分钟, 则返回 14:20:00 +> - 假设当 now 为 14:15:16 , 参考时间为 20 分钟, 则返回 14:20:00 +> +> 当 refer 小于 1 分钟时,将会返回当前时间 + +*** +#### func IntervalFormatSet(intervalType int, str string) + +> 针对 IntervalFormat 函数设置格式化内容 + +*** +#### func IntervalFormat(time1 time.Time, time2 time.Time) string + +> 返回指定时间戳之间的间隔 +> - 使用传入的时间进行计算换算,将结果体现为几年前、几天前、几小时前、几分钟前、几秒前。 + +*** +#### func GetMonthDays(t time.Time) int + +> 获取一个时间当月共有多少天 + +*** +#### func WeekDay(t time.Time) int + +> 获取一个时间是星期几 +> - 1 ~ 7 + +*** +#### func GetNextDayInterval(t time.Time) time.Duration + +> 获取一个时间到下一天间隔多少秒 + +*** +#### func GetToday(t time.Time) time.Time + +> 获取一个时间的今天 + +*** +#### func GetSecond(d time.Duration) int + +> 获取共有多少秒 + +*** +#### func IsSameDay(t1 time.Time, t2 time.Time) bool + +> 两个时间是否是同一天 + +*** +#### func IsSameHour(t1 time.Time, t2 time.Time) bool + +> 两个时间是否是同一小时 + +*** +#### func GetMondayZero(t time.Time) time.Time + +> 获取本周一零点 + +*** +#### func Date(year int, month time.Month, day int) time.Time + +> 返回一个特定日期的时间 + +*** +#### func DateWithHMS(year int, month time.Month, day int, hour int, min int, sec int) time.Time + +> 返回一个精确到秒的时间 + +*** +#### func GetDeltaDay(t1 time.Time, t2 time.Time) int + +> 获取两个时间需要加减的天数 + +*** +#### func GetDeltaWeek(t1 time.Time, t2 time.Time) int + +> 获取两个时间需要加减的周数 + +*** +#### func GetHSMFromString(timeStr string, layout string) (hour int, min int, sec int) + +> 从时间字符串中获取时分秒 + +*** +#### func GetTimeFromString(timeStr string, layout string) time.Time + +> 将时间字符串转化为时间 + +*** +#### func GetDayZero(t time.Time, day int) time.Time + +> 获取 t 增加 day 天后的零点时间 + +*** +#### func GetYesterday(t time.Time) time.Time + +> 获取昨天 + +*** +#### func GetDayLast(t time.Time) time.Time + +> 获取某天的最后一刻 +> - 最后一刻即 23:59:59 + +*** +#### func GetYesterdayLast(t time.Time) time.Time + +> 获取昨天最后一刻 + +*** +#### func GetMinuteStart(t time.Time) time.Time + +> 获取一个时间的 0 秒 + +*** +#### func GetMinuteEnd(t time.Time) time.Time + +> 获取一个时间的 59 秒 + +*** +#### func GetHourStart(t time.Time) time.Time + +> 获取一个时间的 0 分 0 秒 + +*** +#### func GetHourEnd(t time.Time) time.Time + +> 获取一个时间的 59 分 59 秒 + +*** +#### func GetMonthStart(t time.Time) time.Time + +> 获取一个时间的月初 + +*** +#### func GetMonthEnd(t time.Time) time.Time + +> 获取一个时间的月末 + +*** +#### func GetYearStart(t time.Time) time.Time + +> 获取一个时间的年初 + +*** +#### func GetYearEnd(t time.Time) time.Time + +> 获取一个时间的年末 + +*** +#### func NewStateLine(zero State) *StateLine[State] + +> 创建一个从左向右由早到晚的状态时间线 + +
+查看 / 收起单元测试 + + +```go + +func TestNewStateLine(t *testing.T) { + sl := times.NewStateLine(0) + sl.AddState(1, time.Now()) + sl.AddState(2, time.Now().Add(-times.Hour)) + sl.Range(func(index int, state int, ts time.Time) bool { + t.Log(index, state, ts) + return true + }) + t.Log(sl.GetStateByTime(time.Now())) +} + +``` + + +
+ + +*** +#### func SetGlobalTimeOffset(offset time.Duration) + +> 设置全局时间偏移量 + +
+查看 / 收起单元测试 + + +```go + +func TestSetGlobalTimeOffset(t *testing.T) { + fmt.Println(time.Now()) + times.SetGlobalTimeOffset(-times.Hour) + fmt.Println(time.Now()) + times.SetGlobalTimeOffset(times.Hour) + fmt.Println(time.Now()) + fmt.Println(times.NowByNotOffset()) +} + +``` + + +
+ + +*** +#### func NowByNotOffset() time.Time + +> 获取未偏移的当前时间 + +*** +#### func GetGlobalTimeOffset() time.Duration + +> 获取全局时间偏移量 + +*** +#### func ResetGlobalTimeOffset() + +> 重置全局时间偏移量 + +*** +#### func NewPeriod(start time.Time, end time.Time) Period + +> 创建一个时间段 +> - 如果 start 比 end 晚,则会自动交换两个时间 + +*** +#### func NewPeriodWindow(t time.Time, size time.Duration) Period + +> 创建一个特定长度的时间窗口 + +
+查看 / 收起单元测试 + + +```go + +func TestNewPeriodWindow(t *testing.T) { + cur := time.Now() + fmt.Println(cur) + window := times.NewPeriodWindow(cur, times.Day) + fmt.Println(window) +} + +``` + + +
+ + +*** +#### func NewPeriodWindowWeek(t time.Time) Period + +> 创建一周长度的时间窗口,从周一零点开始至周日 23:59:59 结束 + +*** +#### func NewPeriodWithTimeArray(times [2]time.Time) Period + +> 创建一个时间段 + +*** +#### func NewPeriodWithDayZero(t time.Time, day int) Period + +> 创建一个时间段,从 t 开始,持续到 day 天后的 0 点 + +*** +#### func NewPeriodWithDay(t time.Time, day int) Period + +> 创建一个时间段,从 t 开始,持续 day 天 + +*** +#### func NewPeriodWithHour(t time.Time, hour int) Period + +> 创建一个时间段,从 t 开始,持续 hour 小时 + +*** +#### func NewPeriodWithMinute(t time.Time, minute int) Period + +> 创建一个时间段,从 t 开始,持续 minute 分钟 + +*** +#### func NewPeriodWithSecond(t time.Time, second int) Period + +> 创建一个时间段,从 t 开始,持续 second 秒 + +*** +#### func NewPeriodWithMillisecond(t time.Time, millisecond int) Period + +> 创建一个时间段,从 t 开始,持续 millisecond 毫秒 + +*** +#### func NewPeriodWithMicrosecond(t time.Time, microsecond int) Period + +> 创建一个时间段,从 t 开始,持续 microsecond 微秒 + +*** +#### func NewPeriodWithNanosecond(t time.Time, nanosecond int) Period + +> 创建一个时间段,从 t 开始,持续 nanosecond 纳秒 + +*** +### StateLine `STRUCT` +状态时间线 +```go +type StateLine[State generic.Basic] struct { + states []State + points []time.Time + trigger [][]func() +} +``` +#### func (*StateLine) Check(missingAllowed bool, states ...State) bool +> 根据状态顺序检查时间线是否合法 +> - missingAllowed: 是否允许状态缺失,如果为 true,则状态可以不连续,如果为 false,则状态必须连续 +> +> 状态不连续表示时间线中存在状态缺失,例如: +> - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 true,则状态为 [1, 3, 5] 也是合法的 +> - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 false,则状态为 [1, 3, 5] 是不合法的 +*** +#### func (*StateLine) GetMissingStates(states ...State) []State +> 获取缺失的状态 +*** +#### func (*StateLine) HasState(state State) bool +> 检查时间线中是否包含指定状态 +*** +#### func (*StateLine) String() string +> 获取时间线的字符串表示 +*** +#### func (*StateLine) AddState(state State, t time.Time, onTrigger ...func ()) *StateLine[State] +> 添加一个状态到时间线中,状态不能与任一时间点重合,否则将被忽略 +> - onTrigger: 该状态绑定的触发器,该触发器不会被主动执行,需要主动获取触发器执行 +*** +#### func (*StateLine) GetTimeByState(state State) time.Time +> 获取指定状态的时间点 +*** +#### func (*StateLine) GetNextTimeByState(state State) time.Time +> 获取指定状态的下一个时间点 +*** +#### func (*StateLine) GetLastState() State +> 获取最后一个状态 +*** +#### func (*StateLine) GetPrevTimeByState(state State) time.Time +> 获取指定状态的上一个时间点 +*** +#### func (*StateLine) GetIndexByState(state State) int +> 获取指定状态的索引 +*** +#### func (*StateLine) GetStateByTime(t time.Time) State +> 获取指定时间点的状态 +*** +#### func (*StateLine) GetTimeByIndex(index int) time.Time +> 获取指定索引的时间点 +*** +#### func (*StateLine) Move(d time.Duration) *StateLine[State] +> 时间线整体移动 +*** +#### func (*StateLine) GetNextStateTimeByIndex(index int) time.Time +> 获取指定索引的下一个时间点 +*** +#### func (*StateLine) GetPrevStateTimeByIndex(index int) time.Time +> 获取指定索引的上一个时间点 +*** +#### func (*StateLine) GetStateIndexByTime(t time.Time) int +> 获取指定时间点的索引 +*** +#### func (*StateLine) GetStateCount() int +> 获取状态数量 +*** +#### func (*StateLine) GetStateByIndex(index int) State +> 获取指定索引的状态 +*** +#### func (*StateLine) GetTriggerByTime(t time.Time) []func () +> 获取指定时间点的触发器 +*** +#### func (*StateLine) GetTriggerByIndex(index int) []func () +> 获取指定索引的触发器 +*** +#### func (*StateLine) GetTriggerByState(state State) []func () +> 获取指定状态的触发器 +*** +#### func (*StateLine) AddTriggerToState(state State, onTrigger ...func ()) *StateLine[State] +> 给指定状态添加触发器 +*** +#### func (*StateLine) Range(handler func (index int, state State, t time.Time) bool) +> 按照时间顺序遍历时间线 +*** +#### func (*StateLine) RangeReverse(handler func (index int, state State, t time.Time) bool) +> 按照时间逆序遍历时间线 +*** +### Period `STRUCT` +表示一个时间段 +```go +type Period [2]time.Time +``` +#### func (Period) Start() time.Time +> 返回时间段的开始时间 +*** +#### func (Period) End() time.Time +> 返回时间段的结束时间 +*** +#### func (Period) Duration() time.Duration +> 返回时间段的持续时间 +*** +#### func (Period) Day() int +> 返回时间段的持续天数 +*** +#### func (Period) Hour() int +> 返回时间段的持续小时数 +*** +#### func (Period) Minute() int +> 返回时间段的持续分钟数 +*** +#### func (Period) Seconds() int +> 返回时间段的持续秒数 +*** +#### func (Period) Milliseconds() int +> 返回时间段的持续毫秒数 +*** +#### func (Period) Microseconds() int +> 返回时间段的持续微秒数 +*** +#### func (Period) Nanoseconds() int +> 返回时间段的持续纳秒数 +*** +#### func (Period) IsZero() bool +> 判断时间段是否为零值 +*** +#### func (Period) IsInvalid() bool +> 判断时间段是否无效 +*** +#### func (Period) IsBefore(t time.Time) bool +> 判断时间段是否在指定时间之前 +*** +#### func (Period) IsAfter(t time.Time) bool +> 判断时间段是否在指定时间之后 +*** +#### func (Period) IsBetween(t time.Time) bool +> 判断指定时间是否在时间段之间 +*** +#### func (Period) IsOngoing(t time.Time) bool +> 判断指定时间是否正在进行时 +> - 如果时间段的开始时间在指定时间之前或者等于指定时间,且时间段的结束时间在指定时间之后,则返回 true +*** +#### func (Period) IsBetweenOrEqual(t time.Time) bool +> 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间 +*** +#### func (Period) IsBetweenOrEqualPeriod(t Period) bool +> 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间 +*** +#### func (Period) IsOverlap(t Period) bool +> 判断时间段是否与指定时间段重叠 +*** diff --git a/utils/xlsxtool/README.md b/utils/xlsxtool/README.md new file mode 100644 index 0000000..2fefedb --- /dev/null +++ b/utils/xlsxtool/README.md @@ -0,0 +1,32 @@ +# Xlsxtool + +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur) +![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat) + +暂无介绍... + + +## 目录导航 +列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️ +
+展开 / 折叠目录导航 + + +> 包级函数定义 + +|函数名称|描述 +|:--|:-- +|[GetSheetMatrix](#GetSheetMatrix)|将sheet转换为二维矩阵 + + + +
+ + +*** +## 详情信息 +#### func GetSheetMatrix(sheet *xlsx.Sheet) *matrix.Matrix[*xlsx.Cell] + +> 将sheet转换为二维矩阵 + +***