diff --git a/server/internal/v2/conn.go b/server/internal/v2/conn.go
index 4c7632e..8ef69ef 100644
--- a/server/internal/v2/conn.go
+++ b/server/internal/v2/conn.go
@@ -9,14 +9,14 @@ import (
type ConnWriter func(packet Packet) error
type Conn interface {
- // SetActor 设置连接使用的 Actor 名称
- SetActor(actor string)
+ // SetQueue 设置连接使用的消息队列名称
+ SetQueue(queue string)
- // DelActor 删除连接使用的 Actor
- DelActor()
+ // DelQueue 删除连接使用的消息队列,删除后连接将在系统队列执行消息
+ DelQueue()
- // GetActor 获取连接使用的 Actor 名称
- GetActor() string
+ // GetQueue 获取连接使用的消息队列名称
+ GetQueue() string
// WritePacket 写入一个 Packet
WritePacket(packet Packet) error
@@ -43,19 +43,19 @@ type conn struct {
server *server
conn net.Conn // 连接
writer ConnWriter // 写入器
- actor atomic.Pointer[string] // Actor 名称
+ queue atomic.Pointer[string] // Actor 名称
}
-func (c *conn) SetActor(actor string) {
- c.actor.Store(&actor)
+func (c *conn) SetQueue(queue string) {
+ c.queue.Store(&queue)
}
-func (c *conn) DelActor() {
- c.actor.Store(nil)
+func (c *conn) DelQueue() {
+ c.queue.Store(nil)
}
-func (c *conn) GetActor() string {
- ident := c.actor.Load()
+func (c *conn) GetQueue() string {
+ ident := c.queue.Load()
if ident == nil {
return ""
}
diff --git a/server/internal/v2/controller.go b/server/internal/v2/controller.go
index 36dd680..5fd9abd 100644
--- a/server/internal/v2/controller.go
+++ b/server/internal/v2/controller.go
@@ -40,7 +40,7 @@ func (s *controller) GetAnts() *ants.Pool {
}
func (s *controller) RegisterConnection(conn net.Conn, writer ConnWriter) {
- s.server.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context, srv Server) {
+ s.server.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
c := newConn(s.server, conn, writer)
s.server.connections[conn] = c
s.events.onConnectionOpened(c)
@@ -48,7 +48,7 @@ func (s *controller) RegisterConnection(conn net.Conn, writer ConnWriter) {
}
func (s *controller) EliminateConnection(conn net.Conn, err error) {
- s.server.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context, srv Server) {
+ s.server.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
c, exist := s.server.connections[conn]
if !exist {
return
@@ -59,12 +59,12 @@ func (s *controller) EliminateConnection(conn net.Conn, err error) {
}
func (s *controller) ReactPacket(conn net.Conn, packet Packet) {
- s.server.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context, srv Server) {
+ s.server.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
c, exist := s.server.connections[conn]
if !exist {
return
}
- s.PublishSyncMessage(c.GetActor(), func(ctx context.Context, srv Server) {
+ s.PublishSyncMessage(c.GetQueue(), func(ctx context.Context) {
s.events.onConnectionReceivePacket(c, packet)
})
})
diff --git a/server/internal/v2/events.go b/server/internal/v2/events.go
index 3711fd4..b30dd54 100644
--- a/server/internal/v2/events.go
+++ b/server/internal/v2/events.go
@@ -3,7 +3,6 @@ package server
import (
"context"
"fmt"
- "github.com/kercylan98/minotaur/server/internal/v2/message/messages"
"github.com/kercylan98/minotaur/utils/log"
"reflect"
"time"
@@ -68,17 +67,12 @@ func (s *events) onLaunched() {
opt.logger.Info("Minotaur Server", log.String("", "============================================================================"))
})
- s.PublishMessage(messages.Synchronous(
- Server(s.server),
- newProducer(s.server, nil),
- s.server.getSysQueue(),
- func(ctx context.Context, broker Server) {
- s.launchedEventHandlers.RangeValue(func(index int, value LaunchedEventHandler) bool {
- value(s.server, s.server.state.Ip, s.server.state.LaunchedAt)
- return true
- })
- },
- ))
+ s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
+ s.launchedEventHandlers.RangeValue(func(index int, value LaunchedEventHandler) bool {
+ value(s.server, s.server.state.Ip, s.server.state.LaunchedAt)
+ return true
+ })
+ })
}
func (s *events) RegisterConnectionOpenedEvent(handler ConnectionOpenedEventHandler, priority ...int) {
@@ -86,9 +80,9 @@ func (s *events) RegisterConnectionOpenedEvent(handler ConnectionOpenedEventHand
}
func (s *events) onConnectionOpened(conn Conn) {
- s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context, srv Server) {
+ s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
s.connectionOpenedEventHandlers.RangeValue(func(index int, value ConnectionOpenedEventHandler) bool {
- value(srv, conn)
+ value(s, conn)
return true
})
})
@@ -99,9 +93,9 @@ func (s *events) RegisterConnectionClosedEvent(handler ConnectionClosedEventHand
}
func (s *events) onConnectionClosed(conn Conn, err error) {
- s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context, srv Server) {
+ s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
s.connectionClosedEventHandlers.RangeValue(func(index int, value ConnectionClosedEventHandler) bool {
- value(srv, conn, err)
+ value(s, conn, err)
return true
})
})
@@ -112,9 +106,9 @@ func (s *events) RegisterConnectionReceivePacketEvent(handler ConnectionReceiveP
}
func (s *events) onConnectionReceivePacket(conn *conn, packet Packet) {
- s.PublishSyncMessage(conn.GetActor(), func(ctx context.Context, srv Server) {
+ s.PublishSyncMessage(conn.GetQueue(), func(ctx context.Context) {
s.connectionReceivePacketEventHandlers.RangeValue(func(index int, value ConnectionReceivePacketEventHandler) bool {
- value(srv, conn, packet)
+ value(s, conn, packet)
return true
})
})
@@ -125,9 +119,9 @@ func (s *events) RegisterShutdownEvent(handler ShutdownEventHandler, priority ..
}
func (s *events) onShutdown() {
- s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context, srv Server) {
+ s.PublishSyncMessage(s.getSysQueue(), func(ctx context.Context) {
s.shutdownEventHandlers.RangeValue(func(index int, value ShutdownEventHandler) bool {
- value(srv)
+ value(s)
return true
})
})
diff --git a/server/internal/v2/message/broker.go b/server/internal/v2/message/broker.go
deleted file mode 100644
index 027ac89..0000000
--- a/server/internal/v2/message/broker.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package message
-
-// Broker 消息核心的接口定义
-type Broker[P Producer, Q Queue] interface {
- PublishMessage(message Message[P, Q])
-}
diff --git a/server/internal/v2/message/message.go b/server/internal/v2/message/message.go
deleted file mode 100644
index 39d3485..0000000
--- a/server/internal/v2/message/message.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package message
-
-import (
- "context"
- "github.com/kercylan98/minotaur/server/internal/v2/queue"
-)
-
-type Message[P Producer, Q Queue] interface {
- OnInitialize(ctx context.Context)
- OnProcess()
-
- // GetProducer 获取消息生产者
- GetProducer() P
-
- queue.Message[Q]
-}
diff --git a/server/internal/v2/message/messages/asynchronous.go b/server/internal/v2/message/messages/asynchronous.go
deleted file mode 100644
index 4ca4275..0000000
--- a/server/internal/v2/message/messages/asynchronous.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package messages
-
-import (
- "context"
- "github.com/kercylan98/minotaur/server/internal/v2/message"
- "github.com/kercylan98/minotaur/server/internal/v2/queue"
-)
-
-type (
- AsynchronousActuator[P message.Producer, Q message.Queue, B message.Broker[P, Q]] func(context.Context, B, func(context.Context, B)) // 负责执行异步消息的执行器
- AsynchronousHandler[P message.Producer, Q message.Queue, B message.Broker[P, Q]] func(context.Context, B) error // 异步消息逻辑处理器
- AsynchronousCallbackHandler[P message.Producer, Q message.Queue, B message.Broker[P, Q]] func(context.Context, B, error) // 异步消息回调处理器
-)
-
-// Asynchronous 创建一个异步消息实例,并指定相应的处理器。
-// 该函数接收以下参数:
-// - broker:消息所属的 Broker 实例。
-// - actuator:异步消息的执行器,负责执行异步消息的逻辑,当该参数为空时,将会使用默认的 go func()。
-// - handler:异步消息的逻辑处理器,用于执行实际的异步消息处理逻辑,可选参数。
-// - callback:异步消息的回调处理器,处理消息处理完成后的回调操作,可选参数。
-// - afterHandler:异步消息执行完成后的处理器,用于进行后续的处理操作,可选参数。
-//
-// 该函数除了 handler,其他所有处理器均为同步执行
-//
-// 返回值为一个实现了 Message 接口的异步消息实例。
-func Asynchronous[P message.Producer, Q message.Queue, B message.Broker[P, Q]](
- broker B, producer P, queue Q,
- actuator AsynchronousActuator[P, Q, B],
- handler AsynchronousHandler[P, Q, B],
- callback AsynchronousCallbackHandler[P, Q, B],
-) message.Message[P, Q] {
- m := &asynchronous[P, Q, B]{
- broker: broker,
- producer: producer,
- queue: queue,
- actuator: actuator,
- handler: handler,
- callback: callback,
- }
- if m.actuator == nil {
- m.actuator = func(ctx context.Context, b B, f func(context.Context, B)) {
- go f(ctx, b)
- }
- }
-
- return m
-}
-
-type asynchronous[P message.Producer, Q message.Queue, B message.Broker[P, Q]] struct {
- broker B
- producer P
- queue Q
- ctx context.Context
- actuator AsynchronousActuator[P, Q, B]
- handler AsynchronousHandler[P, Q, B]
- callback AsynchronousCallbackHandler[P, Q, B]
-}
-
-func (s *asynchronous[P, Q, B]) OnPublished(controller queue.Controller) {
- controller.IncrementCustomMessageCount(1)
-}
-
-func (s *asynchronous[P, Q, B]) OnProcessed(controller queue.Controller) {
- controller.IncrementCustomMessageCount(-1)
-}
-
-func (s *asynchronous[P, Q, B]) OnInitialize(ctx context.Context) {
- s.ctx = ctx
-}
-
-func (s *asynchronous[P, Q, B]) OnProcess() {
- s.actuator(s.ctx, s.broker, func(ctx context.Context, broker B) {
- var err error
- if s.handler != nil {
- err = s.handler(s.ctx, s.broker)
- }
-
- broker.PublishMessage(Synchronous(broker, s.producer, s.queue, func(ctx context.Context, broker B) {
- if s.callback != nil {
- s.callback(ctx, broker, err)
- }
- }))
- })
-}
-
-func (s *asynchronous[P, Q, B]) GetProducer() P {
- return s.producer
-}
-
-func (s *asynchronous[P, Q, B]) GetQueue() Q {
- return s.queue
-}
diff --git a/server/internal/v2/message/messages/synchronous.go b/server/internal/v2/message/messages/synchronous.go
deleted file mode 100644
index bb32677..0000000
--- a/server/internal/v2/message/messages/synchronous.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package messages
-
-import (
- "context"
- "github.com/kercylan98/minotaur/server/internal/v2/message"
- "github.com/kercylan98/minotaur/server/internal/v2/queue"
-)
-
-type (
- SynchronousHandler[P message.Producer, Q message.Queue, B message.Broker[P, Q]] func(context.Context, B)
-)
-
-func Synchronous[P message.Producer, Q message.Queue, B message.Broker[P, Q]](
- broker B, producer P, queue Q,
- handler SynchronousHandler[P, Q, B],
-) message.Message[P, Q] {
- return &synchronous[P, Q, B]{
- broker: broker,
- producer: producer,
- queue: queue,
- handler: handler,
- }
-}
-
-type synchronous[P message.Producer, Q message.Queue, B message.Broker[P, Q]] struct {
- broker B
- producer P
- queue Q
- ctx context.Context
- handler SynchronousHandler[P, Q, B]
-}
-
-func (s *synchronous[P, Q, B]) OnPublished(controller queue.Controller) {
-
-}
-
-func (s *synchronous[P, Q, B]) OnProcessed(controller queue.Controller) {
-
-}
-
-func (s *synchronous[P, Q, B]) OnInitialize(ctx context.Context) {
- s.ctx = ctx
-}
-
-func (s *synchronous[P, Q, B]) OnProcess() {
- s.handler(s.ctx, s.broker)
-}
-
-func (s *synchronous[P, Q, B]) GetProducer() P {
- return s.producer
-}
-
-func (s *synchronous[P, Q, B]) GetQueue() Q {
- return s.queue
-}
diff --git a/server/internal/v2/message/producer.go b/server/internal/v2/message/producer.go
deleted file mode 100644
index 0691634..0000000
--- a/server/internal/v2/message/producer.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package message
-
-type Producer interface {
-
-}
diff --git a/server/internal/v2/message/queue.go b/server/internal/v2/message/queue.go
deleted file mode 100644
index c3c0233..0000000
--- a/server/internal/v2/message/queue.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package message
-
-type Queue comparable
diff --git a/server/internal/v2/options.go b/server/internal/v2/options.go
index 8d282c8..9ef9562 100644
--- a/server/internal/v2/options.go
+++ b/server/internal/v2/options.go
@@ -1,7 +1,6 @@
package server
import (
- "github.com/kercylan98/minotaur/server/internal/v2/message"
"github.com/kercylan98/minotaur/utils/log/v2"
"os"
"sync"
@@ -18,7 +17,6 @@ func DefaultOptions() *Options {
actorMessageChannelSize: 1024,
serverMessageBufferInitialSize: 1024,
actorMessageBufferInitialSize: 1024,
- messageErrorHandler: nil,
lifeCycleLimit: 0,
logger: log.NewLogger(log.NewHandler(os.Stdout, log.DefaultOptions().WithCallerSkip(-1).WithLevel(log.LevelInfo))),
}
@@ -27,16 +25,15 @@ func DefaultOptions() *Options {
type Options struct {
server *server
rw sync.RWMutex
- serverMessageChannelSize int // 服务器 Actor 消息处理管道大小
- actorMessageChannelSize int // Actor 消息处理管道大小
- serverMessageBufferInitialSize int // 服务器 Actor 消息写入缓冲区初始化大小
- actorMessageBufferInitialSize int // Actor 消息写入缓冲区初始化大小
- messageErrorHandler func(srv Server, message message.Message[Producer, string], err error) // 消息错误处理器
- lifeCycleLimit time.Duration // 服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
- logger *log.Logger // 日志记录器
- debug bool // Debug 模式
- syncLowMessageDuration time.Duration // 同步慢消息时间
- asyncLowMessageDuration time.Duration // 异步慢消息时间
+ serverMessageChannelSize int // 服务器 Actor 消息处理管道大小
+ actorMessageChannelSize int // Actor 消息处理管道大小
+ serverMessageBufferInitialSize int // 服务器 Actor 消息写入缓冲区初始化大小
+ actorMessageBufferInitialSize int // Actor 消息写入缓冲区初始化大小
+ lifeCycleLimit time.Duration // 服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
+ logger *log.Logger // 日志记录器
+ debug bool // Debug 模式
+ syncLowMessageDuration time.Duration // 同步慢消息时间
+ asyncLowMessageDuration time.Duration // 异步慢消息时间
}
func (opt *Options) init(srv *server) *Options {
@@ -54,7 +51,6 @@ func (opt *Options) Apply(options ...*Options) {
opt.actorMessageChannelSize = option.actorMessageChannelSize
opt.serverMessageBufferInitialSize = option.serverMessageBufferInitialSize
opt.actorMessageBufferInitialSize = option.actorMessageBufferInitialSize
- opt.messageErrorHandler = option.messageErrorHandler
opt.lifeCycleLimit = option.lifeCycleLimit
opt.logger = option.logger
opt.debug = option.debug
@@ -65,9 +61,6 @@ func (opt *Options) Apply(options ...*Options) {
}
if opt.server != nil && !opt.server.state.LaunchedAt.IsZero() {
opt.active()
- if opt.server.reactor != nil {
- opt.server.reactor.SetLogger(opt.logger)
- }
}
}
@@ -121,9 +114,6 @@ func (opt *Options) IsDebug() bool {
func (opt *Options) WithLogger(logger *log.Logger) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.logger = logger
- if opt.server != nil && opt.server.reactor != nil {
- opt.server.reactor.SetLogger(opt.logger)
- }
})
}
@@ -188,20 +178,6 @@ func (opt *Options) GetActorMessageBufferInitialSize() int {
})
}
-// WithMessageErrorHandler 设置消息错误处理器,当消息处理出现错误时,会调用该处理器进行处理
-// - 如果在运行时设置,后续消息错误将会使用新的 handler 进行处理
-func (opt *Options) WithMessageErrorHandler(handler func(srv Server, message message.Message[Producer, string], err error)) *Options {
- return opt.modifyOptionsValue(func(opt *Options) {
- opt.messageErrorHandler = handler
- })
-}
-
-func (opt *Options) GetMessageErrorHandler() func(srv Server, message message.Message[Producer, string], err error) {
- return getOptionsValue(opt, func(opt *Options) func(srv Server, message message.Message[Producer, string], err error) {
- return opt.messageErrorHandler
- })
-}
-
// WithLifeCycleLimit 设置服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
// - 如果设置为 <= 0 的值,将不限制服务器生命周期
// - 该函数支持运行时设置
diff --git a/server/internal/v2/queue/controller.go b/server/internal/v2/queue/controller.go
deleted file mode 100644
index 37dc5ff..0000000
--- a/server/internal/v2/queue/controller.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package queue
-
-type (
- incrementCustomMessageCountHandler func(delta int64)
-)
-
-func newController[Id, Q comparable, M Message[Q]](queue *Queue[Id, Q, M], message Message[Q]) Controller {
- return Controller{
- incrementCustomMessageCount: func(delta int64) {
- queueName := message.GetQueue()
-
- queue.cond.L.Lock()
-
- currIdent := queue.identifiers[queueName]
- currIdent += delta
- queue.identifiers[queueName] = currIdent
- queue.state.total += delta
- //log.Info("消息总计数", log.Int64("计数", q.state.total))
-
- queue.cond.Signal()
- queue.cond.L.Unlock()
- },
- }
-}
-
-// Controller 队列控制器
-type Controller struct {
- incrementCustomMessageCount incrementCustomMessageCountHandler
-}
-
-// IncrementCustomMessageCount 增加自定义消息计数,当消息计数不为 > 0 时会导致队列关闭进入等待状态
-func (c Controller) IncrementCustomMessageCount(delta int64) {
- c.incrementCustomMessageCount(delta)
-}
diff --git a/server/internal/v2/queue/errors.go b/server/internal/v2/queue/errors.go
deleted file mode 100644
index 9675217..0000000
--- a/server/internal/v2/queue/errors.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package queue
-
-import (
- "errors"
-)
-
-var (
- ErrorQueueClosed = errors.New("queue closed") // 队列已关闭
- ErrorQueueInvalid = errors.New("queue invalid") // 无效的队列
-)
diff --git a/server/internal/v2/queue/message.go b/server/internal/v2/queue/message.go
deleted file mode 100644
index 3bea6f2..0000000
--- a/server/internal/v2/queue/message.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package queue
-
-// Message 消息接口定义
-type Message[Queue comparable] interface {
- // GetQueue 获取消息执行队列
- GetQueue() Queue
- // OnPublished 消息发布成功
- OnPublished(controller Controller)
- // OnProcessed 消息处理完成
- OnProcessed(controller Controller)
-}
diff --git a/server/internal/v2/queue/message_handler.go b/server/internal/v2/queue/message_handler.go
deleted file mode 100644
index 011ea39..0000000
--- a/server/internal/v2/queue/message_handler.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package queue
-
-// MessageHandler 消息处理器支持传入两个函数对消息进行处理
-// - 在 handler 内可以执行对消息的逻辑
-// - 在 finisher 函数中可以接收到该消息是否是最后一条消息
-type MessageHandler[Id, Q comparable, M Message[Q]] func(
- handler func(m M),
- finisher func(m M, last bool),
-)
diff --git a/server/internal/v2/queue/queue.go b/server/internal/v2/queue/queue.go
deleted file mode 100644
index 2ab5ae8..0000000
--- a/server/internal/v2/queue/queue.go
+++ /dev/null
@@ -1,174 +0,0 @@
-package queue
-
-import (
- "github.com/kercylan98/minotaur/utils/buffer"
- "sync"
- "sync/atomic"
-)
-
-// New 创建一个并发安全的队列 Queue,该队列支持通过 chanSize 自定义读取 channel 的大小,同支持使用 bufferSize 指定 buffer.Ring 的初始大小
-func New[Id, Q comparable, M Message[Q]](id Id, chanSize, bufferSize int) *Queue[Id, Q, M] {
- q := &Queue[Id, Q, M]{
- c: make(chan MessageHandler[Id, Q, M], chanSize),
- buf: buffer.NewRing[wrapper[Id, Q, M]](bufferSize),
- condRW: &sync.RWMutex{},
- identifiers: make(map[Q]int64),
- }
- q.cond = sync.NewCond(q.condRW)
- q.state = &State[Id, Q, M]{
- queue: q,
- id: id,
- status: StatusNone,
- }
- return q
-}
-
-// Queue 队列是一个适用于消息处理等场景的并发安全的数据结构
-// - 该队列接收自定义的消息 M,并将消息有序的传入 Read 函数所返回的 channel 中以供处理
-// - 该结构主要实现目标为读写分离且并发安全的非阻塞传输队列,当消费阻塞时以牺牲内存为代价换取消息的生产不阻塞,适用于服务器消息处理等
-// - 该队列保证了消息的完整性,确保消息不丢失,在队列关闭后会等待所有消息处理完毕后进行关闭,并提供 SetClosedHandler 函数来监听队列的关闭信号
-type Queue[Id, Q comparable, M Message[Q]] struct {
- state *State[Id, Q, M] // 队列状态信息
- c chan MessageHandler[Id, Q, M] // 消息读取通道
- buf *buffer.Ring[wrapper[Id, Q, M]] // 消息缓冲区
- cond *sync.Cond // 条件变量
- condRW *sync.RWMutex // 条件变量的读写锁
- closedHandler func(q *Queue[Id, Q, M]) // 关闭处理函数
- identifiers map[Q]int64 // 标识符在队列的消息计数映射
-}
-
-// Id 获取队列 Id
-func (q *Queue[Id, Q, M]) Id() Id {
- return q.state.id
-}
-
-// SetId 设置队列 Id
-func (q *Queue[Id, Q, M]) SetId(id Id) {
- q.state.id = id
-}
-
-// SetClosedHandler 设置队列关闭处理函数,在队列关闭后将执行该函数。此刻队列不再可用
-// - Close 函数为非阻塞调用,调用后不会立即关闭队列,会等待消息处理完毕且处理期间不再有新消息介入
-func (q *Queue[Id, Q, M]) SetClosedHandler(handler func(q *Queue[Id, Q, M])) {
- q.closedHandler = handler
-}
-
-// Run 阻塞的运行队列,当队列非首次运行时,将会引发来自 ErrorQueueInvalid 的 panic
-func (q *Queue[Id, Q, M]) Run() {
- if atomic.LoadInt32(&q.state.status) != StatusNone {
- panic(ErrorQueueInvalid)
- }
- atomic.StoreInt32(&q.state.status, StatusRunning)
- defer func(q *Queue[Id, Q, M]) {
- if q.closedHandler != nil {
- q.closedHandler(q)
- }
- }(q)
- for {
- q.cond.L.Lock()
- for q.buf.IsEmpty() {
- if atomic.LoadInt32(&q.state.status) >= StatusClosing && q.state.total == 0 {
- q.cond.L.Unlock()
- atomic.StoreInt32(&q.state.status, StatusClosed)
- close(q.c)
- return
- }
- q.cond.Wait()
- }
- items := q.buf.ReadAll()
- q.cond.L.Unlock()
- for i := 0; i < len(items); i++ {
- item := items[i]
- q.c <- func(handler func(m M), finisher func(m M, last bool)) {
- defer func(msg M) {
- msg.OnProcessed(item.controller)
-
- queue := msg.GetQueue()
-
- q.cond.L.Lock()
- q.state.total--
- curr := q.identifiers[queue] - 1
- if curr != 0 {
- q.identifiers[queue] = curr
- } else {
- delete(q.identifiers, queue)
- }
- if finisher != nil {
- finisher(msg, curr == 0)
- }
- //log.Info("消息总计数", log.Int64("计数", q.state.total))
- q.cond.Signal()
- q.cond.L.Unlock()
- }(item.message)
-
- handler(item.message)
- }
- }
- }
-}
-
-// Push 向队列中推送来自 queue 的消息 m,当队列已关闭时将会返回 ErrorQueueClosed
-func (q *Queue[Id, Q, M]) Push(queue Q, m M) error {
- if atomic.LoadInt32(&q.state.status) > StatusClosing {
- return ErrorQueueClosed
- }
- wrapper := newWrapper(q, m)
-
- q.cond.L.Lock()
- q.identifiers[queue]++
- q.state.total++
- q.buf.Write(wrapper)
- //log.Info("消息总计数", log.Int64("计数", q.state.total))
- q.cond.Signal()
- q.cond.L.Unlock()
-
- m.OnPublished(wrapper.controller)
- return nil
-}
-
-// WaitAdd 向队列增加来自外部的等待计数,在队列关闭时会等待该计数归零
-func (q *Queue[Id, Q, M]) WaitAdd(queue Q, delta int64) {
- q.cond.L.Lock()
-
- currIdent := q.identifiers[queue]
- currIdent += delta
- q.identifiers[queue] = currIdent
- q.state.total += delta
- //log.Info("消息总计数", log.Int64("计数", q.state.total))
-
- q.cond.Signal()
- q.cond.L.Unlock()
-}
-
-// Read 获取队列消息的只读通道,
-func (q *Queue[Id, Q, M]) Read() <-chan MessageHandler[Id, Q, M] {
- return q.c
-}
-
-// Close 关闭队列
-func (q *Queue[Id, Q, M]) Close() {
- atomic.CompareAndSwapInt32(&q.state.status, StatusRunning, StatusClosing)
- q.cond.Broadcast()
-}
-
-// State 获取队列状态
-func (q *Queue[Id, Q, M]) State() *State[Id, Q, M] {
- return q.state
-}
-
-// GetMessageCount 获取消息数量
-func (q *Queue[Id, Q, M]) GetMessageCount() (count int64) {
- q.condRW.RLock()
- defer q.condRW.RUnlock()
- for _, i := range q.identifiers {
- count += i
- }
- return
-}
-
-// GetMessageCountWithIdent 获取特定消息人的消息数量
-func (q *Queue[Id, Q, M]) GetMessageCountWithIdent(queue Q) int64 {
- q.condRW.RLock()
- defer q.condRW.RUnlock()
- return q.identifiers[queue]
-}
diff --git a/server/internal/v2/queue/state.go b/server/internal/v2/queue/state.go
deleted file mode 100644
index fcf024d..0000000
--- a/server/internal/v2/queue/state.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package queue
-
-import (
- "sync/atomic"
-)
-
-const (
- StatusNone = iota - 1 // 队列未运行
- StatusRunning // 队列运行中
- StatusClosing // 队列关闭中
- StatusClosed // 队列已关闭
-)
-
-type State[Id, Q comparable, M Message[Q]] struct {
- queue *Queue[Id, Q, M]
- id Id // 队列 ID
- status int32 // 状态标志
- total int64 // 消息总计数
-}
-
-// IsClosed 判断队列是否已关闭
-func (q *State[Id, Q, M]) IsClosed() bool {
- return atomic.LoadInt32(&q.status) == StatusClosed
-}
-
-// IsClosing 判断队列是否正在关闭
-func (q *State[Id, Q, M]) IsClosing() bool {
- return atomic.LoadInt32(&q.status) == StatusClosing
-}
-
-// IsRunning 判断队列是否正在运行
-func (q *State[Id, Q, M]) IsRunning() bool {
- return atomic.LoadInt32(&q.status) == StatusRunning
-}
diff --git a/server/internal/v2/queue/wrapper.go b/server/internal/v2/queue/wrapper.go
deleted file mode 100644
index d81471d..0000000
--- a/server/internal/v2/queue/wrapper.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package queue
-
-func newWrapper[Id, Q comparable, M Message[Q]](queue *Queue[Id, Q, M], message M) wrapper[Id, Q, M] {
- return wrapper[Id, Q, M]{
- message: message,
- controller: newController[Id, Q, M](queue, message),
- }
-}
-
-type wrapper[Id, Q comparable, M Message[Q]] struct {
- message M
- controller Controller
-}
diff --git a/server/internal/v2/reactor/handlers.go b/server/internal/v2/reactor/handlers.go
deleted file mode 100644
index 755ef8a..0000000
--- a/server/internal/v2/reactor/handlers.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package reactor
-
-import "github.com/kercylan98/minotaur/server/internal/v2/queue"
-
-type MessageHandler[Q comparable, M queue.Message[Q]] func(message M)
-
-type ErrorHandler[Q comparable, M queue.Message[Q]] func(message M, err error)
diff --git a/server/internal/v2/reactor/reactor.go b/server/internal/v2/reactor/reactor.go
deleted file mode 100644
index c537721..0000000
--- a/server/internal/v2/reactor/reactor.go
+++ /dev/null
@@ -1,209 +0,0 @@
-package reactor
-
-import (
- "fmt"
- "github.com/kercylan98/minotaur/server/internal/v2/loadbalancer"
- "github.com/kercylan98/minotaur/server/internal/v2/queue"
- "github.com/kercylan98/minotaur/utils/log/v2"
- "github.com/kercylan98/minotaur/utils/super"
- "runtime"
- "runtime/debug"
- "sync"
- "sync/atomic"
-)
-
-const (
- statusNone = iota - 1 // 事件循环未运行
- statusRunning // 事件循环运行中
- statusClosing // 事件循环关闭中
- statusClosed // 事件循环已关闭
-)
-
-// NewReactor 创建一个新的 Reactor 实例,初始化系统级别的队列和多个 Socket 对应的队列
-func NewReactor[Queue comparable, M queue.Message[Queue]](queueSize, queueBufferSize int, handler MessageHandler[Queue, M], errorHandler ErrorHandler[Queue, M]) *Reactor[Queue, M] {
- if handler == nil {
-
- }
- r := &Reactor[Queue, M]{
- lb: loadbalancer.NewRoundRobin[int, *queue.Queue[int, Queue, M]](),
- errorHandler: errorHandler,
- queueSize: queueSize,
- queueBufferSize: queueBufferSize,
- state: statusNone,
- handler: handler,
- location: make(map[Queue]int),
- }
- r.logger.Store(log.GetLogger())
-
- defaultNum := runtime.NumCPU()
- r.queueRW.Lock()
- for i := 0; i < defaultNum; i++ {
- r.noneLockAddQueue()
- }
- r.queueRW.Unlock()
-
- return r
-}
-
-// Reactor 是一个消息反应器,管理系统级别的队列和多个 Socket 对应的队列
-type Reactor[Queue comparable, M queue.Message[Queue]] struct {
- logger atomic.Pointer[log.Logger] // 日志记录器
- state int32 // 状态
- queueSize int // 队列管道大小
- queueBufferSize int // 队列缓冲区大小
- queues []*queue.Queue[int, Queue, M] // 所有使用的队列
- queueRW sync.RWMutex // 队列读写锁
- location map[Queue]int // 所在队列 ID 映射
- locationRW sync.RWMutex // 所在队列 ID 映射锁
- lb *loadbalancer.RoundRobin[int, *queue.Queue[int, Queue, M]] // 负载均衡器
- wg sync.WaitGroup // 等待组
- cwg sync.WaitGroup // 关闭等待组
- handler MessageHandler[Queue, M] // 消息处理器
- errorHandler ErrorHandler[Queue, M] // 错误处理器
-}
-
-// SetLogger 设置日志记录器
-func (r *Reactor[Queue, M]) SetLogger(logger *log.Logger) {
- r.logger.Store(logger)
-}
-
-// GetLogger 获取日志记录器
-func (r *Reactor[Queue, M]) GetLogger() *log.Logger {
- return r.logger.Load()
-}
-
-// process 消息处理
-func (r *Reactor[Queue, M]) process(msg M) {
- defer func(msg M) {
- if err := super.RecoverTransform(recover()); err != nil {
- if r.errorHandler != nil {
- r.errorHandler(msg, err)
- } else {
- r.GetLogger().Error("Reactor", log.String("action", "process"), log.Any("queue", msg.GetQueue()), log.Err(err))
- debug.PrintStack()
- }
- }
- }(msg)
-
- r.handler(msg)
-}
-
-// Dispatch 将消息分发到 ident 使用的队列,当 ident 首次使用时,将会根据负载均衡策略选择一个队列
-// - 设置 count 会增加消息的外部计数,当 Reactor 关闭时会等待外部计数归零
-// - 当 ident 为空字符串时候,将发送到
-func (r *Reactor[Queue, M]) Dispatch(ident Queue, msg M) error {
- r.queueRW.RLock()
- if atomic.LoadInt32(&r.state) > statusClosing {
- r.queueRW.RUnlock()
- return fmt.Errorf("reactor closing or closed")
- }
-
- var next *queue.Queue[int, Queue, M]
- r.locationRW.RLock()
- i, exist := r.location[ident]
- r.locationRW.RUnlock()
- if !exist {
- r.locationRW.Lock()
- if i, exist = r.location[ident]; !exist {
- next = r.lb.Next()
- r.location[ident] = next.Id()
- r.logger.Load().Debug("Reactor", log.String("action", "bind"), log.Any("ident", ident), log.Any("queue", next.Id()))
- } else {
- next = r.queues[i]
- }
- r.locationRW.Unlock()
- } else {
- next = r.queues[i]
- }
- r.queueRW.RUnlock()
- return next.Push(ident, msg)
-}
-
-// Run 启动 Reactor,运行系统级别的队列和多个 Socket 对应的队列
-func (r *Reactor[Queue, M]) Run(callbacks ...func(queues []*queue.Queue[int, Queue, M])) {
- if !atomic.CompareAndSwapInt32(&r.state, statusNone, statusRunning) {
- return
- }
- r.queueRW.Lock()
- queues := r.queues
- for _, q := range queues {
- r.runQueue(q)
- }
- r.queueRW.Unlock()
- for _, callback := range callbacks {
- callback(queues)
- }
- r.wg.Wait()
-}
-
-func (r *Reactor[Queue, M]) noneLockAddQueue() {
- q := queue.New[int, Queue, M](len(r.queues), r.queueSize, r.queueBufferSize)
- r.lb.Add(q) // 运行前添加到负载均衡器,未运行时允许接收消息
- r.queues = append(r.queues, q)
-}
-
-func (r *Reactor[Queue, M]) noneLockDelQueue(q *queue.Queue[int, Queue, M]) {
- idx := q.Id()
- if idx < 0 || idx >= len(r.queues) || r.queues[idx] != q {
- return
- }
- r.queues = append(r.queues[:idx], r.queues[idx+1:]...)
- for i := idx; i < len(r.queues); i++ {
- r.queues[i].SetId(i)
- }
-}
-
-func (r *Reactor[Queue, M]) runQueue(q *queue.Queue[int, Queue, M]) {
- r.wg.Add(1)
- q.SetClosedHandler(func(q *queue.Queue[int, Queue, M]) {
- // 关闭时正在等待关闭完成,外部已加锁,无需再次加锁
- r.noneLockDelQueue(q)
- r.cwg.Done()
- r.logger.Load().Debug("Reactor", log.String("action", "close"), log.Any("queue", q.Id()))
- })
- go q.Run()
-
- go func(r *Reactor[Queue, M], q *queue.Queue[int, Queue, M]) {
- defer r.wg.Done()
- for m := range q.Read() {
- m(r.process, r.processFinish)
- }
- }(r, q)
-}
-
-func (r *Reactor[Queue, M]) Close() {
- if !atomic.CompareAndSwapInt32(&r.state, statusRunning, statusClosing) {
- return
- }
- r.queueRW.Lock()
- r.cwg.Add(len(r.queues) + 1)
- for _, q := range r.queues {
- q.Close()
- }
- r.cwg.Wait()
- atomic.StoreInt32(&r.state, statusClosed)
- r.queueRW.Unlock()
-}
-
-func (r *Reactor[Queue, M]) processFinish(m M, last bool) {
- if !last {
- return
- }
- queueName := m.GetQueue()
-
- r.locationRW.RLock()
- mq, exist := r.location[queueName]
- r.locationRW.RUnlock()
- if exist {
- r.locationRW.Lock()
- defer r.locationRW.Unlock()
- mq, exist = r.location[queueName]
- if exist {
- delete(r.location, queueName)
- r.queueRW.RLock()
- mq := r.queues[mq]
- r.queueRW.RUnlock()
- r.logger.Load().Debug("Reactor", log.String("action", "unbind"), log.Any("queueName", queueName), log.Any("queue", mq.Id()))
- }
- }
-}
diff --git a/server/internal/v2/reactor/reactor_test.go b/server/internal/v2/reactor/reactor_test.go
deleted file mode 100644
index e557d9f..0000000
--- a/server/internal/v2/reactor/reactor_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package reactor_test
-
-import (
- "github.com/kercylan98/minotaur/server/internal/v2/queue"
- "github.com/kercylan98/minotaur/server/internal/v2/reactor"
- "github.com/kercylan98/minotaur/utils/log/v2"
- "github.com/kercylan98/minotaur/utils/random"
- "github.com/kercylan98/minotaur/utils/times"
- "os"
- "testing"
- "time"
-)
-
-func BenchmarkReactor_Dispatch(b *testing.B) {
- var r = reactor.NewReactor(1024*16, 1024, 1024, 1024, func(message queue.MessageWrapper[int, string, func()]) {
- message.Message()
- }, func(message queue.MessageWrapper[int, string, func()], err error) {
-
- })
-
- r.SetLogger(log.NewLogger(log.NewHandler(os.Stdout, log.DefaultOptions().WithLevel(log.LevelInfo))))
-
- go r.Run()
-
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- if err := r.IdentDispatch(random.HostName(), func() {
- }); err != nil {
- return
- }
- }
- })
-}
-
-func TestReactor_Dispatch(t *testing.T) {
- var r = reactor.NewReactor(1024*16, 1024, 1024, 1024, func(message queue.MessageWrapper[int, string, func()]) {
- message.Message()
- }, func(message queue.MessageWrapper[int, string, func()], err error) {
-
- })
-
- go r.Run()
-
- for i := 0; i < 10000; i++ {
- go func() {
- id := random.HostName()
- for {
- time.Sleep(time.Millisecond * 20)
- if err := r.IdentDispatch(id, func() {
-
- }); err != nil {
- return
- }
- }
- }()
- }
-
- time.Sleep(times.Second)
- r.Close()
-}
diff --git a/server/internal/v2/server.go b/server/internal/v2/server.go
index 771a3a9..39ccf4b 100644
--- a/server/internal/v2/server.go
+++ b/server/internal/v2/server.go
@@ -3,9 +3,10 @@ package server
import (
"context"
"fmt"
- "github.com/kercylan98/minotaur/server/internal/v2/message"
- "github.com/kercylan98/minotaur/server/internal/v2/message/messages"
- "github.com/kercylan98/minotaur/server/internal/v2/queue"
+ "github.com/kercylan98/minotaur/toolkit/message"
+ "github.com/kercylan98/minotaur/toolkit/message/brokers"
+ messageEvents "github.com/kercylan98/minotaur/toolkit/message/events"
+ "github.com/kercylan98/minotaur/toolkit/message/queues"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/log/v2"
"github.com/kercylan98/minotaur/utils/random"
@@ -13,13 +14,11 @@ import (
"reflect"
"time"
- "github.com/kercylan98/minotaur/server/internal/v2/reactor"
"github.com/kercylan98/minotaur/utils/network"
)
type Server interface {
Events
- message.Broker[Producer, string]
// Run 运行服务器
Run() error
@@ -31,10 +30,10 @@ type Server interface {
GetStatus() *State
// PublishSyncMessage 发布同步消息
- PublishSyncMessage(queue string, handler messages.SynchronousHandler[Producer, string, Server])
+ PublishSyncMessage(topic string, handler messageEvents.SynchronousHandler)
// PublishAsyncMessage 发布异步消息,当包含多个 callback 时,仅首个生效
- PublishAsyncMessage(queue string, handler messages.AsynchronousHandler[Producer, string, Server], callback ...messages.AsynchronousCallbackHandler[Producer, string, Server])
+ PublishAsyncMessage(topic string, handler messageEvents.AsynchronousHandler, callback ...messageEvents.AsynchronousCallbackHandler)
}
type server struct {
@@ -48,7 +47,7 @@ type server struct {
ctx context.Context
cancel context.CancelFunc
network Network
- reactor *reactor.Reactor[string, message.Message[Producer, string]]
+ broker message.Broker[int, string]
}
func NewServer(network Network, options ...*Options) Server {
@@ -62,17 +61,12 @@ func NewServer(network Network, options ...*Options) Server {
srv.controller = new(controller).init(srv)
srv.events = new(events).init(srv)
srv.state = new(State).init(srv)
- srv.reactor = reactor.NewReactor[string, message.Message[Producer, string]](
- srv.GetActorMessageChannelSize(),
- srv.GetActorMessageBufferInitialSize(),
- srv.onProcessMessage,
- func(message message.Message[Producer, string], err error) {
- if handler := srv.GetMessageErrorHandler(); handler != nil {
- handler(srv, message, err)
- }
- })
+ srv.broker = brokers.NewSparseGoroutine(func(index int) message.Queue[int, string] {
+ return queues.NewNonBlockingRW[int, string](index, 1024*8, 1024)
+ }, func(handler message.EventExecutor) {
+ handler()
+ })
srv.Options.init(srv).Apply(options...)
- srv.reactor.SetLogger(srv.Options.GetLogger())
antsPool, err := ants.NewPool(ants.DefaultAntsPoolSize, ants.WithOptions(ants.Options{
ExpiryDuration: 10 * time.Second,
Nonblocking: true,
@@ -89,15 +83,7 @@ func NewServer(network Network, options ...*Options) Server {
}
func (s *server) Run() (err error) {
- var queueWait = make(chan struct{})
- go s.reactor.Run(func(queues []*queue.Queue[int, string, message.Message[Producer, string]]) {
- for _, q := range queues {
- s.GetLogger().Debug("Reactor", log.String("action", "run"), log.Any("queue", q.Id()))
- }
- close(queueWait)
- })
- <-queueWait
-
+ go s.broker.Run()
if err = s.network.OnSetup(s.ctx, s); err != nil {
return
}
@@ -125,7 +111,7 @@ func (s *server) Shutdown() (err error) {
DisableHttpPProf()
s.events.onShutdown()
err = s.network.OnShutdown()
- s.reactor.Close()
+ s.broker.Close()
return
}
@@ -133,38 +119,22 @@ func (s *server) getSysQueue() string {
return s.queue
}
-func (s *server) PublishMessage(msg message.Message[Producer, string]) {
- s.reactor.Dispatch(msg.GetQueue(), msg)
+func (s *server) PublishMessage(topic string, event message.Event[int, string]) {
+ s.broker.Publish(topic, event)
}
-func (s *server) PublishSyncMessage(queue string, handler messages.SynchronousHandler[Producer, string, Server]) {
- s.PublishMessage(messages.Synchronous[Producer, string, Server](
- s, newProducer(s, nil), queue,
- handler,
- ))
+func (s *server) PublishSyncMessage(topic string, handler messageEvents.SynchronousHandler) {
+ s.PublishMessage(topic, messageEvents.Synchronous[int, string](handler))
}
-func (s *server) PublishAsyncMessage(queue string, handler messages.AsynchronousHandler[Producer, string, Server], callback ...messages.AsynchronousCallbackHandler[Producer, string, Server]) {
- s.PublishMessage(messages.Asynchronous[Producer, string, Server](
- s, newProducer(s, nil), queue,
- func(ctx context.Context, srv Server, f func(context.Context, Server)) {
- s.ants.Submit(func() {
- f(ctx, s)
- })
- },
- handler,
- collection.FindFirstOrDefaultInSlice(callback, nil),
- ))
+func (s *server) PublishAsyncMessage(topic string, handler messageEvents.AsynchronousHandler, callback ...messageEvents.AsynchronousCallbackHandler) {
+ s.PublishMessage(topic, messageEvents.Asynchronous[int, string](func(ctx context.Context, f func(context.Context)) {
+ s.ants.Submit(func() {
+ f(ctx)
+ })
+ }, handler, collection.FindFirstOrDefaultInSlice(callback, nil)))
}
func (s *server) GetStatus() *State {
return s.state.Status()
}
-
-func (s *server) onProcessMessage(m message.Message[Producer, string]) {
- s.getManyOptions(func(opt *Options) {
- ctx := context.Background()
- m.OnInitialize(ctx)
- m.OnProcess()
- })
-}
diff --git a/server/internal/v2/server_test.go b/server/internal/v2/server_test.go
index 9ff3ccc..947d97d 100644
--- a/server/internal/v2/server_test.go
+++ b/server/internal/v2/server_test.go
@@ -35,9 +35,9 @@ func TestNewServer(t *testing.T) {
t.Error(err)
}
- srv.PublishAsyncMessage("123", func(ctx context.Context, s server.Server) error {
+ srv.PublishAsyncMessage("123", func(ctx context.Context) error {
return nil
- }, func(ctx context.Context, s server.Server, err error) {
+ }, func(ctx context.Context, err error) {
for i := 0; i < 10000000; i++ {
_ = tm["1"]
tm["1"] = random.Bool()
diff --git a/toolkit/chrono/adjuster.go b/toolkit/chrono/adjuster.go
new file mode 100644
index 0000000..8f26ed6
--- /dev/null
+++ b/toolkit/chrono/adjuster.go
@@ -0,0 +1,38 @@
+package chrono
+
+import "time"
+
+// NewAdjuster 创建一个时间调节器
+func NewAdjuster(adjust time.Duration) *Adjuster {
+ return &Adjuster{adjust: adjust}
+}
+
+// Adjuster 时间调节器是一个用于获取偏移时间的工具
+type Adjuster struct {
+ adjust time.Duration
+}
+
+// Adjust 获取偏移调整的时间量
+func (a *Adjuster) Adjust() time.Duration {
+ return a.adjust
+}
+
+// SetAdjust 设置偏移调整的时间量
+func (a *Adjuster) SetAdjust(adjust time.Duration) {
+ a.adjust = adjust
+}
+
+// AddAdjust 增加偏移调整的时间量
+func (a *Adjuster) AddAdjust(adjust time.Duration) {
+ a.adjust += adjust
+}
+
+// Now 获取经过偏移调整的当前时间
+func (a *Adjuster) Now() time.Time {
+ return time.Now().Add(a.adjust)
+}
+
+// Since 获取经过偏移调整的时间间隔
+func (a *Adjuster) Since(t time.Time) time.Duration {
+ return a.Now().Sub(t)
+}
diff --git a/toolkit/chrono/adjuster_built_in.go b/toolkit/chrono/adjuster_built_in.go
new file mode 100644
index 0000000..30b0eb9
--- /dev/null
+++ b/toolkit/chrono/adjuster_built_in.go
@@ -0,0 +1,26 @@
+package chrono
+
+import "time"
+
+var (
+ builtInAdjuster *Adjuster
+)
+
+func init() {
+ builtInAdjuster = NewAdjuster(0)
+}
+
+// BuiltInAdjuster 获取内置的由 NewAdjuster(0) 函数创建的时间调节器
+func BuiltInAdjuster() *Adjuster {
+ return builtInAdjuster
+}
+
+// Now 调用内置时间调节器 BuiltInAdjuster 的 Adjuster.Now 函数
+func Now() time.Time {
+ return BuiltInAdjuster().Now()
+}
+
+// Since 调用内置时间调节器 BuiltInAdjuster 的 Adjuster.Since 函数
+func Since(t time.Time) time.Duration {
+ return BuiltInAdjuster().Since(t)
+}
diff --git a/toolkit/chrono/constants.go b/toolkit/chrono/constants.go
new file mode 100644
index 0000000..12df9ec
--- /dev/null
+++ b/toolkit/chrono/constants.go
@@ -0,0 +1,14 @@
+package chrono
+
+import "time"
+
+const (
+ Nanosecond = time.Nanosecond
+ Microsecond = time.Microsecond
+ Millisecond = time.Millisecond
+ Second = time.Second
+ Minute = time.Minute
+ Hour = time.Hour
+ Day = Hour * 24
+ Week = Day * 7
+)
diff --git a/toolkit/chrono/moment.go b/toolkit/chrono/moment.go
new file mode 100644
index 0000000..c7ddff1
--- /dev/null
+++ b/toolkit/chrono/moment.go
@@ -0,0 +1,350 @@
+package chrono
+
+import (
+ "github.com/kercylan98/minotaur/utils/generic"
+ "math"
+ "time"
+)
+
+var zero = time.Time{}
+
+// IsMomentReached 检查指定时刻是否已到达且未发生过
+// - now: 当前时间
+// - last: 上次发生的时间
+// - hour: 要检查的时刻的小时数
+// - min: 要检查的时刻的分钟数
+// - sec: 要检查的时刻的秒数
+func IsMomentReached(now time.Time, last time.Time, hour, min, sec int) bool {
+ moment := time.Date(last.Year(), last.Month(), last.Day(), hour, min, sec, 0, time.Local)
+ if !moment.Before(now) {
+ return false
+ } else if moment.After(last) {
+ return true
+ }
+
+ // 如果要检查的时刻在上次发生的时间和当前时间之间,并且已经过了一天,说明已经发生
+ nextDayMoment := moment.AddDate(0, 0, 1)
+ return !nextDayMoment.After(now)
+}
+
+// GetNextMoment 获取下一个指定时刻发生的时间。
+func GetNextMoment(now time.Time, hour, min, sec int) time.Time {
+ moment := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, time.Local)
+ // 如果要检查的时刻已经过了,则返回明天的这个时刻
+ if moment.Before(now) {
+ moment = moment.AddDate(0, 0, 1)
+ }
+ return moment
+}
+
+// IsMomentInDays 检查指定时刻是否在给定的天数内发生。
+// - now: 当前时间
+// - hour: 要检查的时刻的小时数
+// - min: 要检查的时刻的分钟数
+// - sec: 要检查的时刻的秒数
+// - days: 表示要偏移的天数。正数表示未来,负数表示过去,0 即今天
+func IsMomentInDays(now time.Time, hour, min, sec, days int) bool {
+ offsetTime := now.AddDate(0, 0, days)
+ moment := time.Date(offsetTime.Year(), offsetTime.Month(), offsetTime.Day(), hour, min, sec, 0, time.Local)
+ return now.Before(moment.AddDate(0, 0, 1)) && now.After(moment)
+}
+
+// IsMomentYesterday 检查指定时刻是否在昨天发生
+func IsMomentYesterday(now time.Time, hour, min, sec int) bool {
+ return IsMomentInDays(now, hour, min, sec, -1)
+}
+
+// IsMomentToday 检查指定时刻是否在今天发生
+func IsMomentToday(now time.Time, hour, min, sec int) bool {
+ return IsMomentInDays(now, hour, min, sec, 0)
+}
+
+// IsMomentTomorrow 检查指定时刻是否在明天发生
+func IsMomentTomorrow(now time.Time, hour, min, sec int) bool {
+ return IsMomentInDays(now, hour, min, sec, 1)
+}
+
+// IsMomentPassed 检查指定时刻是否已经过去
+func IsMomentPassed(now time.Time, hour, min, sec int) bool {
+ // 构建要检查的时刻
+ moment := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, time.Local)
+ return now.After(moment)
+}
+
+// IsMomentFuture 检查指定时刻是否在未来
+func IsMomentFuture(now time.Time, hour, min, sec int) bool {
+ // 构建要检查的时刻
+ moment := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, time.Local)
+ return now.Before(moment)
+}
+
+// GetStartOfDay 获取指定时间的当天第一刻,即 00:00:00
+func GetStartOfDay(t time.Time) time.Time {
+ return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
+}
+
+// GetEndOfDay 获取指定时间的当天最后一刻,即 23:59:59
+func GetEndOfDay(t time.Time) time.Time {
+ return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, t.Location())
+}
+
+// GetRelativeStartOfDay 获取相对于指定时间减去或加上指定天数后的当天开始时间
+// - offsetDays: 要偏移的天数,负数表示过去的某一天,正数表示未来的某一天
+func GetRelativeStartOfDay(t time.Time, offsetDays int) time.Time {
+ return GetStartOfDay(GetStartOfDay(t.AddDate(0, 0, offsetDays)))
+}
+
+// GetRelativeEndOfDay 获取相对于指定时间减去或加上指定天数后的当天结束时间
+// - offsetDays: 要偏移的天数,负数表示过去的某一天,正数表示未来的某一天
+func GetRelativeEndOfDay(t time.Time, offsetDays int) time.Time {
+ return GetEndOfDay(GetEndOfDay(t.AddDate(0, 0, offsetDays)))
+}
+
+// GetStartOfWeek 获取指定时间所在周的特定周的开始时刻,即 00:00:00
+func GetStartOfWeek(t time.Time, weekday time.Weekday) time.Time {
+ t = GetStartOfDay(t)
+ tw := t.Weekday()
+ if tw == 0 {
+ tw = 7
+ }
+ d := 1 - int(tw)
+ switch weekday {
+ case time.Sunday:
+ d += 6
+ default:
+ d += int(weekday) - 1
+ }
+ return t.AddDate(0, 0, d)
+}
+
+// GetEndOfWeek 获取指定时间所在周的特定周的最后时刻,即 23:59:59
+func GetEndOfWeek(t time.Time, weekday time.Weekday) time.Time {
+ return GetEndOfDay(GetStartOfWeek(t, weekday))
+}
+
+// GetRelativeStartOfWeek 获取相对于当前时间的本周开始时间,以指定的星期作为一周的开始,并根据需要进行周数的偏移
+// - now:当前时间
+// - week:以哪一天作为一周的开始
+// - offsetWeeks:要偏移的周数,正数表示向未来偏移,负数表示向过去偏移
+//
+// 该函数返回以指定星期作为一周的开始时间,然后根据偏移量进行周数偏移,得到相对于当前时间的周的开始时间
+//
+// 假设 week 为 time.Saturday 且 offsetWeeks 为 -1,则表示获取上周六的开始时间,一下情况中第一个时间为 now,第二个时间为函数返回值
+// - 2024-03-01 00:00:00 --相对时间-> 2024-02-24 00:00:00 --偏移时间--> 2024-02-17 00:00:00
+// - 2024-03-02 00:00:00 --相对时间-> 2024-02-24 00:00:00 --偏移时间--> 2024-02-17 00:00:00
+// - 2024-03-03 00:00:00 --相对时间-> 2024-03-02 00:00:00 --偏移时间--> 2024-02-24 00:00:00
+func GetRelativeStartOfWeek(now time.Time, week time.Weekday, offsetWeeks int) time.Time {
+ nowWeekday, weekday := int(now.Weekday()), int(week)
+ if nowWeekday == 0 {
+ nowWeekday = 7
+ }
+ if weekday == 0 {
+ weekday = 7
+ }
+ if nowWeekday < weekday {
+ now = now.Add(-Week)
+ }
+ moment := GetStartOfWeek(now, week)
+ return moment.Add(Week * time.Duration(offsetWeeks))
+}
+
+// GetRelativeEndOfWeek 获取相对于当前时间的本周结束时间,以指定的星期作为一周的开始,并根据需要进行周数的偏移
+// - 该函数详细解释参考 GetRelativeEndOfWeek 函数,其中不同的是,该函数返回的是这一天最后一刻的时间,即 23:59:59
+func GetRelativeEndOfWeek(now time.Time, week time.Weekday, offsetWeeks int) time.Time {
+ return GetEndOfDay(GetRelativeStartOfWeek(now, week, offsetWeeks))
+}
+
+// GetRelativeTimeOfWeek 获取相对于当前时间的本周指定星期的指定时刻,以指定的星期作为一周的开始,并根据需要进行周数的偏移
+// - 该函数详细解释参考 GetRelativeStartOfWeek 函数,其中不同的是,该函数返回的是这一天对应 now 的时间
+func GetRelativeTimeOfWeek(now time.Time, week time.Weekday, offsetWeeks int) time.Time {
+ moment := GetRelativeStartOfWeek(now, week, offsetWeeks)
+ return time.Date(moment.Year(), moment.Month(), moment.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location())
+}
+
+// Zero 获取一个零值的时间
+func Zero() time.Time {
+ return zero
+}
+
+// IsZero 检查一个时间是否为零值
+func IsZero(t time.Time) bool {
+ return t.IsZero()
+}
+
+// Max 获取两个时间中的最大值
+func Max(t1, t2 time.Time) time.Time {
+ if t1.After(t2) {
+ return t1
+ }
+ return t2
+}
+
+// Min 获取两个时间中的最小值
+func Min(t1, t2 time.Time) time.Time {
+ if t1.Before(t2) {
+ return t1
+ }
+ return t2
+}
+
+// SmallerFirst 将两个时间按照从小到大的顺序排列
+func SmallerFirst(t1, t2 time.Time) (time.Time, time.Time) {
+ if t1.Before(t2) {
+ return t1, t2
+ }
+ return t2, t1
+}
+
+// SmallerLast 将两个时间按照从大到小的顺序排列
+func SmallerLast(t1, t2 time.Time) (time.Time, time.Time) {
+ if t1.Before(t2) {
+ return t2, t1
+ }
+ return t1, t2
+}
+
+// Delta 获取两个时间之间的时间差
+func Delta(t1, t2 time.Time) time.Duration {
+ if t1.Before(t2) {
+ return t2.Sub(t1)
+ }
+ return t1.Sub(t2)
+}
+
+// FloorDeltaDays 计算两个时间之间的天数差异,并向下取整
+func FloorDeltaDays(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Day)
+}
+
+// CeilDeltaDays 计算两个时间之间的天数差异,并向上取整
+func CeilDeltaDays(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(math.Ceil(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Day)))
+}
+
+// RoundDeltaDays 计算两个时间之间的天数差异,并四舍五入
+func RoundDeltaDays(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(math.Round(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Day)))
+}
+
+// FloorDeltaHours 计算两个时间之间的小时数差异,并向下取整
+func FloorDeltaHours(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Hour)
+}
+
+// CeilDeltaHours 计算两个时间之间的小时数差异,并向上取整
+func CeilDeltaHours(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(math.Ceil(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Hour)))
+}
+
+// RoundDeltaHours 计算两个时间之间的小时数差异,并四舍五入
+func RoundDeltaHours(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(math.Round(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Hour)))
+}
+
+// FloorDeltaMinutes 计算两个时间之间的分钟数差异,并向下取整
+func FloorDeltaMinutes(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Minute)
+}
+
+// CeilDeltaMinutes 计算两个时间之间的分钟数差异,并向上取整
+func CeilDeltaMinutes(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(math.Ceil(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Minute)))
+}
+
+// RoundDeltaMinutes 计算两个时间之间的分钟数差异,并四舍五入
+func RoundDeltaMinutes(t1, t2 time.Time) int {
+ t1, t2 = SmallerFirst(t1, t2)
+ return int(math.Round(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Minute)))
+}
+
+// IsSameSecond 检查两个时间是否在同一秒
+func IsSameSecond(t1, t2 time.Time) bool {
+ return t1.Unix() == t2.Unix()
+}
+
+// IsSameMinute 检查两个时间是否在同一分钟
+func IsSameMinute(t1, t2 time.Time) bool {
+ return t1.Minute() == t2.Minute() && IsSameHour(t1, t2)
+}
+
+// IsSameHour 检查两个时间是否在同一小时
+func IsSameHour(t1, t2 time.Time) bool {
+ return t1.Hour() == t2.Hour() && IsSameDay(t1, t2)
+}
+
+// IsSameDay 检查两个时间是否在同一天
+func IsSameDay(t1, t2 time.Time) bool {
+ return GetStartOfDay(t1).Equal(GetStartOfDay(t2))
+}
+
+// IsSameWeek 检查两个时间是否在同一周
+func IsSameWeek(t1, t2 time.Time) bool {
+ return GetStartOfWeek(t1, time.Monday).Equal(GetStartOfWeek(t2, time.Monday))
+}
+
+// IsSameMonth 检查两个时间是否在同一月
+func IsSameMonth(t1, t2 time.Time) bool {
+ return t1.Month() == t2.Month() && t1.Year() == t2.Year()
+}
+
+// IsSameYear 检查两个时间是否在同一年
+func IsSameYear(t1, t2 time.Time) bool {
+ return t1.Year() == t2.Year()
+}
+
+// GetMonthDays 获取指定时间所在月的天数
+func GetMonthDays(t time.Time) int {
+ year, month, _ := t.Date()
+ if month != 2 {
+ if month == 4 || month == 6 || month == 9 || month == 11 {
+ return 30
+ }
+ return 31
+ }
+ if ((year%4 == 0) && (year%100 != 0)) || year%400 == 0 {
+ return 29
+ }
+ return 28
+}
+
+// ToDuration 将一个数值转换为 time.Duration 类型,当 unit 为空时,默认为纳秒单位
+func ToDuration[V generic.Number](v V, unit ...time.Duration) time.Duration {
+ var u = Nanosecond
+ if len(unit) > 0 {
+ u = unit[0]
+ }
+ return time.Duration(v) * u
+}
+
+// ToDurationSecond 将一个数值转换为秒的 time.Duration 类型
+func ToDurationSecond[V generic.Number](v V) time.Duration {
+ return ToDuration(v, Second)
+}
+
+// ToDurationMinute 将一个数值转换为分钟的 time.Duration 类型
+func ToDurationMinute[V generic.Number](v V) time.Duration {
+ return ToDuration(v, Minute)
+}
+
+// ToDurationHour 将一个数值转换为小时的 time.Duration 类型
+func ToDurationHour[V generic.Number](v V) time.Duration {
+ return ToDuration(v, Hour)
+}
+
+// ToDurationDay 将一个数值转换为天的 time.Duration 类型
+func ToDurationDay[V generic.Number](v V) time.Duration {
+ return ToDuration(v, Day)
+}
+
+// ToDurationWeek 将一个数值转换为周的 time.Duration 类型
+func ToDurationWeek[V generic.Number](v V) time.Duration {
+ return ToDuration(v, Week)
+}
diff --git a/toolkit/chrono/period.go b/toolkit/chrono/period.go
new file mode 100644
index 0000000..288fe67
--- /dev/null
+++ b/toolkit/chrono/period.go
@@ -0,0 +1,172 @@
+package chrono
+
+import (
+ "time"
+)
+
+// NewPeriod 创建一个时间段
+// - 如果 start 比 end 晚,则会自动交换两个时间
+func NewPeriod(start, end time.Time) Period {
+ if start.After(end) {
+ start, end = end, start
+ }
+ return Period{start, end}
+}
+
+// NewPeriodWindow 创建一个特定长度的时间窗口
+func NewPeriodWindow(t time.Time, size time.Duration) Period {
+ start := t.Truncate(size)
+ end := start.Add(size)
+ return Period{start, end}
+}
+
+// NewPeriodWindowWeek 创建一周长度的时间窗口,从周一零点开始至周日 23:59:59 结束
+func NewPeriodWindowWeek(t time.Time) Period {
+ var start = GetStartOfWeek(t, time.Monday)
+ end := start.Add(Week)
+ return Period{start, end}
+}
+
+// NewPeriodWithTimeArray 创建一个时间段
+func NewPeriodWithTimeArray(times [2]time.Time) Period {
+ return NewPeriod(times[0], times[1])
+}
+
+// NewPeriodWithDayZero 创建一个时间段,从 t 开始,持续到 day 天后的 0 点
+func NewPeriodWithDayZero(t time.Time, day int) Period {
+ return NewPeriod(t, GetStartOfDay(t.AddDate(0, 0, day)))
+}
+
+// NewPeriodWithDay 创建一个时间段,从 t 开始,持续 day 天
+func NewPeriodWithDay(t time.Time, day int) Period {
+ return NewPeriod(t, t.AddDate(0, 0, day))
+}
+
+// NewPeriodWithHour 创建一个时间段,从 t 开始,持续 hour 小时
+func NewPeriodWithHour(t time.Time, hour int) Period {
+ return NewPeriod(t, t.Add(time.Duration(hour)*time.Hour))
+}
+
+// NewPeriodWithMinute 创建一个时间段,从 t 开始,持续 minute 分钟
+func NewPeriodWithMinute(t time.Time, minute int) Period {
+ return NewPeriod(t, t.Add(time.Duration(minute)*time.Minute))
+}
+
+// NewPeriodWithSecond 创建一个时间段,从 t 开始,持续 second 秒
+func NewPeriodWithSecond(t time.Time, second int) Period {
+ return NewPeriod(t, t.Add(time.Duration(second)*time.Second))
+}
+
+// NewPeriodWithMillisecond 创建一个时间段,从 t 开始,持续 millisecond 毫秒
+func NewPeriodWithMillisecond(t time.Time, millisecond int) Period {
+ return NewPeriod(t, t.Add(time.Duration(millisecond)*time.Millisecond))
+}
+
+// NewPeriodWithMicrosecond 创建一个时间段,从 t 开始,持续 microsecond 微秒
+func NewPeriodWithMicrosecond(t time.Time, microsecond int) Period {
+ return NewPeriod(t, t.Add(time.Duration(microsecond)*time.Microsecond))
+}
+
+// NewPeriodWithNanosecond 创建一个时间段,从 t 开始,持续 nanosecond 纳秒
+func NewPeriodWithNanosecond(t time.Time, nanosecond int) Period {
+ return NewPeriod(t, t.Add(time.Duration(nanosecond)*time.Nanosecond))
+}
+
+// Period 表示一个时间段
+type Period [2]time.Time
+
+// Start 返回时间段的开始时间
+func (p Period) Start() time.Time {
+ return p[0]
+}
+
+// End 返回时间段的结束时间
+func (p Period) End() time.Time {
+ return p[1]
+}
+
+// Duration 返回时间段的持续时间
+func (p Period) Duration() time.Duration {
+ return p[1].Sub(p[0])
+}
+
+// Days 返回时间段的持续天数
+func (p Period) Days() int {
+ return int(p.Duration().Hours() / 24)
+}
+
+// Hours 返回时间段的持续小时数
+func (p Period) Hours() int {
+ return int(p.Duration().Hours())
+}
+
+// Minutes 返回时间段的持续分钟数
+func (p Period) Minutes() int {
+ return int(p.Duration().Minutes())
+}
+
+// Seconds 返回时间段的持续秒数
+func (p Period) Seconds() int {
+ return int(p.Duration().Seconds())
+}
+
+// Milliseconds 返回时间段的持续毫秒数
+func (p Period) Milliseconds() int {
+ return int(p.Duration().Milliseconds())
+}
+
+// Microseconds 返回时间段的持续微秒数
+func (p Period) Microseconds() int {
+ return int(p.Duration().Microseconds())
+}
+
+// Nanoseconds 返回时间段的持续纳秒数
+func (p Period) Nanoseconds() int {
+ return int(p.Duration().Nanoseconds())
+}
+
+// IsZero 判断时间段是否为零值
+func (p Period) IsZero() bool {
+ return p[0].IsZero() && p[1].IsZero()
+}
+
+// IsInvalid 判断时间段是否无效
+func (p Period) IsInvalid() bool {
+ return p[0].IsZero() || p[1].IsZero()
+}
+
+// IsBefore 判断时间段是否在指定时间之前
+func (p Period) IsBefore(t time.Time) bool {
+ return p[1].Before(t)
+}
+
+// IsAfter 判断时间段是否在指定时间之后
+func (p Period) IsAfter(t time.Time) bool {
+ return p[0].After(t)
+}
+
+// IsBetween 判断指定时间是否在时间段之间
+func (p Period) IsBetween(t time.Time) bool {
+ return p[0].Before(t) && p[1].After(t)
+}
+
+// IsOngoing 判断指定时间是否正在进行时
+// - 如果时间段的开始时间在指定时间之前或者等于指定时间,且时间段的结束时间在指定时间之后,则返回 true
+func (p Period) IsOngoing(t time.Time) bool {
+ return (p[0].Before(t) || p[0].Equal(t)) && p[1].After(t)
+}
+
+// IsBetweenOrEqual 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间
+func (p Period) IsBetweenOrEqual(t time.Time) bool {
+ return p.IsBetween(t) || p[0].Equal(t) || p[1].Equal(t)
+}
+
+// IsBetweenOrEqualPeriod 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间
+func (p Period) IsBetweenOrEqualPeriod(t Period) bool {
+ return p.IsBetween(t[0]) || p.IsBetween(t[1]) || p[0].Equal(t[0]) || p[1].Equal(t[1])
+}
+
+// IsOverlap 判断时间段是否与指定时间段重叠
+func (p Period) IsOverlap(t Period) bool {
+ return p.IsBetweenOrEqualPeriod(t) || t.IsBetweenOrEqualPeriod(p)
+}
diff --git a/toolkit/chrono/period_test.go b/toolkit/chrono/period_test.go
new file mode 100644
index 0000000..8e8aaaa
--- /dev/null
+++ b/toolkit/chrono/period_test.go
@@ -0,0 +1,15 @@
+package chrono_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/chrono"
+ "testing"
+ "time"
+)
+
+func TestNewPeriodWindow(t *testing.T) {
+ cur := time.Now()
+ fmt.Println(cur)
+ window := chrono.NewPeriodWindow(cur, chrono.Day)
+ fmt.Println(window)
+}
diff --git a/toolkit/chrono/scheduler.go b/toolkit/chrono/scheduler.go
new file mode 100644
index 0000000..d7ed3a1
--- /dev/null
+++ b/toolkit/chrono/scheduler.go
@@ -0,0 +1,218 @@
+package chrono
+
+import (
+ "github.com/RussellLuo/timingwheel"
+ "github.com/gorhill/cronexpr"
+ "github.com/kercylan98/minotaur/utils/collection"
+ "reflect"
+ "sync"
+ "time"
+)
+
+const (
+ DefaultSchedulerTick = SchedulerPoolDefaultTick
+ DefaultSchedulerWheelSize = SchedulerPoolDefaultWheelSize
+)
+
+const (
+ SchedulerForever = -1 // 无限循环
+ SchedulerOnce = 1 // 一次
+ SchedulerInstantly = 0 // 立刻
+)
+
+// NewDefaultScheduler 创建一个默认的时间调度器
+// - tick: DefaultSchedulerTick
+// - wheelSize: DefaultSchedulerWheelSize
+func NewDefaultScheduler() *Scheduler {
+ return NewScheduler(DefaultSchedulerTick, DefaultSchedulerWheelSize)
+}
+
+// NewScheduler 创建一个并发安全的时间调度器
+// - tick: 时间轮的刻度间隔。
+// - wheelSize: 时间轮的大小。
+func NewScheduler(tick time.Duration, wheelSize int64) *Scheduler {
+ return newScheduler(nil, tick, wheelSize)
+}
+
+func newScheduler(pool *SchedulerPool, tick time.Duration, wheelSize int64) *Scheduler {
+ scheduler := &Scheduler{
+ pool: pool,
+ wheel: timingwheel.NewTimingWheel(tick, wheelSize),
+ tasks: make(map[string]*schedulerTask),
+ }
+ if pool != nil {
+ scheduler.generation = pool.getGeneration()
+ }
+ scheduler.wheel.Start()
+ return scheduler
+}
+
+// Scheduler 并发安全的时间调度器
+type Scheduler struct {
+ pool *SchedulerPool // 时间调度器所属的池,当该值为 nil 时,该时间调度器不属于任何池
+ wheel *timingwheel.TimingWheel // 时间轮
+ tasks map[string]*schedulerTask // 所有任务
+ lock sync.RWMutex // 用于确保并发安全的锁
+ generation int64 // 时间调度器的代数
+ tick time.Duration // 时间周期
+
+ executor func(name string, caller func()) // 任务执行器
+}
+
+// SetExecutor 设置任务执行器
+// - 如果该任务执行器来自于时间调度器对象池,那么默认情况下将会使用时间调度器对象池的任务执行器,主动设置将会覆盖默认的任务执行器
+func (s *Scheduler) SetExecutor(executor func(name string, caller func())) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ s.executor = executor
+}
+
+// Release 释放时间调度器,时间调度器被释放后将不再可用,如果时间调度器属于某个池且池未满,则会重新加入到池中
+// - 释放后所有已注册的任务将会被取消
+func (s *Scheduler) Release() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ for name, task := range s.tasks {
+ task.close()
+ delete(s.tasks, name)
+ }
+
+ if s.pool == nil || s.pool.getGeneration() != s.generation {
+ s.wheel.Stop()
+ return
+ }
+
+ s.pool.lock.Lock()
+ if len(s.pool.schedulers) < s.pool.size {
+ s.pool.schedulers = append(s.pool.schedulers, s)
+ s.pool.lock.Unlock()
+ return
+ }
+ s.pool.lock.Unlock()
+ s.wheel.Stop()
+}
+
+// UnregisterTask 取消特定任务的执行计划的注册
+// - 如果任务不存在,则不执行任何操作
+func (s *Scheduler) UnregisterTask(name string) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+ if task, exist := s.tasks[name]; exist {
+ task.close()
+ delete(s.tasks, name)
+ }
+}
+
+// GetRegisteredTasks 获取所有未执行完成的任务名称
+func (s *Scheduler) GetRegisteredTasks() []string {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+ return collection.ConvertMapKeysToSlice(s.tasks)
+}
+
+// RegisterCronTask 通过 cron 表达式注册一个任务。
+// - 当 cron 表达式错误时,将会返回错误信息
+func (s *Scheduler) RegisterCronTask(name, expression string, function interface{}, args ...interface{}) error {
+ expr, err := cronexpr.Parse(expression)
+ if err != nil {
+ return err
+ }
+ s.task(name, 0, 0, expr, 0, function, args...)
+ return nil
+}
+
+// RegisterImmediateCronTask 与 RegisterCronE 相同,但是会立即执行一次
+func (s *Scheduler) RegisterImmediateCronTask(name, expression string, function interface{}, args ...interface{}) error {
+ if err := s.RegisterCronTask(name, expression, function, args...); err != nil {
+ return err
+ }
+ s.call(name, function, args...)
+ return nil
+}
+
+// RegisterAfterTask 注册一个在特定时间后执行一次的任务
+func (s *Scheduler) RegisterAfterTask(name string, after time.Duration, function interface{}, args ...interface{}) {
+ s.task(name, after, s.pool.tick, nil, 1, function, args...)
+}
+
+// RegisterRepeatedTask 注册一个在特定时间后反复执行的任务
+func (s *Scheduler) RegisterRepeatedTask(name string, after, interval time.Duration, times int, function interface{}, args ...interface{}) {
+ s.task(name, after, interval, nil, times, function, args...)
+}
+
+// RegisterDayMomentTask 注册一个在每天特定时刻执行的任务
+// - 其中 lastExecuted 为上次执行时间,adjust 为时间偏移量,hour、min、sec 为时、分、秒
+// - 当上次执行时间被错过时,将会立即执行一次
+func (s *Scheduler) RegisterDayMomentTask(name string, lastExecuted time.Time, offset time.Duration, hour, min, sec int, function interface{}, args ...interface{}) {
+ now := time.Now().Add(offset)
+ if IsMomentReached(now, lastExecuted, hour, min, sec) {
+ s.call(name, function, args...)
+ }
+
+ moment := GetNextMoment(now, hour, min, sec)
+ s.RegisterRepeatedTask(name, moment.Sub(now), time.Hour*24, SchedulerForever, function, args...)
+}
+
+func (s *Scheduler) task(name string, after, interval time.Duration, expr *cronexpr.Expression, times int, function interface{}, args ...interface{}) {
+ s.UnregisterTask(name)
+
+ if expr == nil {
+ if after < s.tick {
+ after = s.tick
+ }
+ if interval < s.tick {
+ interval = s.tick
+ }
+ }
+
+ var values = make([]reflect.Value, len(args))
+ for i, v := range args {
+ values[i] = reflect.ValueOf(v)
+ }
+
+ task := &schedulerTask{
+ name: name,
+ after: after,
+ interval: interval,
+ total: times,
+ function: reflect.ValueOf(function),
+ args: values,
+ scheduler: s,
+ expr: expr,
+ }
+ var executor func(name string, caller func())
+ if s.pool != nil {
+ executor = s.pool.getExecutor()
+ }
+ s.lock.Lock()
+ if s.executor != nil {
+ executor = s.pool.getExecutor()
+ }
+
+ s.tasks[name] = task
+ if executor != nil {
+ task.timer = s.wheel.ScheduleFunc(task, func() {
+ executor(task.Name(), task.caller)
+ })
+ } else {
+ task.timer = s.wheel.ScheduleFunc(task, task.caller)
+ }
+ s.lock.Unlock()
+}
+
+func (s *Scheduler) call(name string, function any, args ...any) {
+ var values = make([]reflect.Value, len(args))
+ for i, v := range args {
+ values[i] = reflect.ValueOf(v)
+ }
+ f := reflect.ValueOf(function)
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+ if s.executor != nil {
+ s.executor(name, func() {
+ f.Call(values)
+ })
+ } else {
+ f.Call(values)
+ }
+}
diff --git a/toolkit/chrono/scheduler_built_in.go b/toolkit/chrono/scheduler_built_in.go
new file mode 100644
index 0000000..c494ae7
--- /dev/null
+++ b/toolkit/chrono/scheduler_built_in.go
@@ -0,0 +1,57 @@
+package chrono
+
+import "time"
+
+const (
+ BuiltInSchedulerWheelSize = 50
+)
+
+var (
+ buildInSchedulerPool *SchedulerPool
+ builtInScheduler *Scheduler
+)
+
+func init() {
+ buildInSchedulerPool = NewDefaultSchedulerPool()
+ builtInScheduler = NewScheduler(DefaultSchedulerTick, BuiltInSchedulerWheelSize)
+}
+
+// BuiltInSchedulerPool 获取内置的由 NewDefaultSchedulerPool 函数创建的时间调度器对象池
+func BuiltInSchedulerPool() *SchedulerPool {
+ return buildInSchedulerPool
+}
+
+// BuiltInScheduler 获取内置的由 NewScheduler(DefaultSchedulerTick, BuiltInSchedulerWheelSize) 创建的时间调度器
+func BuiltInScheduler() *Scheduler {
+ return builtInScheduler
+}
+
+// UnregisterTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.UnregisterTask 函数
+func UnregisterTask(name string) {
+ BuiltInScheduler().UnregisterTask(name)
+}
+
+// RegisterCronTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterCronTask 函数
+func RegisterCronTask(name, expression string, function interface{}, args ...interface{}) error {
+ return BuiltInScheduler().RegisterCronTask(name, expression, function, args...)
+}
+
+// RegisterImmediateCronTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterImmediateCronTask 函数
+func RegisterImmediateCronTask(name, expression string, function interface{}, args ...interface{}) error {
+ return BuiltInScheduler().RegisterImmediateCronTask(name, expression, function, args...)
+}
+
+// RegisterAfterTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterAfterTask 函数
+func RegisterAfterTask(name string, after time.Duration, function interface{}, args ...interface{}) {
+ BuiltInScheduler().RegisterAfterTask(name, after, function, args...)
+}
+
+// RegisterRepeatedTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterRepeatedTask 函数
+func RegisterRepeatedTask(name string, after, interval time.Duration, times int, function interface{}, args ...interface{}) {
+ BuiltInScheduler().RegisterRepeatedTask(name, after, interval, times, function, args...)
+}
+
+// RegisterDayMomentTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterDayMomentTask 函数
+func RegisterDayMomentTask(name string, lastExecuted time.Time, offset time.Duration, hour, min, sec int, function interface{}, args ...interface{}) {
+ BuiltInScheduler().RegisterDayMomentTask(name, lastExecuted, offset, hour, min, sec, function, args...)
+}
diff --git a/toolkit/chrono/scheduler_pool.go b/toolkit/chrono/scheduler_pool.go
new file mode 100644
index 0000000..ede53d4
--- /dev/null
+++ b/toolkit/chrono/scheduler_pool.go
@@ -0,0 +1,110 @@
+package chrono
+
+import (
+ "fmt"
+ "sync"
+ "time"
+)
+
+const (
+ SchedulerPoolDefaultSize = 96
+ SchedulerPoolDefaultTick = time.Millisecond * 10
+ SchedulerPoolDefaultWheelSize = 10
+)
+
+// NewDefaultSchedulerPool 创建一个默认参数的并发安全的时间调度器对象池
+// - size: SchedulerPoolDefaultSize
+// - tick: SchedulerPoolDefaultTick
+// - wheelSize: SchedulerPoolDefaultWheelSize
+func NewDefaultSchedulerPool() *SchedulerPool {
+ scheduler, err := NewSchedulerPool(SchedulerPoolDefaultSize, SchedulerPoolDefaultTick, SchedulerPoolDefaultWheelSize)
+ if err != nil {
+ panic(err) // 该错误不应该发生,用于在参数或实现变更后的提示
+ }
+ return scheduler
+}
+
+// NewSchedulerPool 创建一个并发安全的时间调度器对象池
+func NewSchedulerPool(size int, tick time.Duration, wheelSize int64) (*SchedulerPool, error) {
+ if size <= 0 {
+ return nil, fmt.Errorf("scheduler pool size must greater than 0, got: %d", size)
+ }
+ if wheelSize <= 0 {
+ return nil, fmt.Errorf("scheduler pool wheelSize must greater than 0, got: %d", size)
+ }
+ return &SchedulerPool{
+ size: size,
+ tick: tick,
+ wheelSize: wheelSize,
+ generation: 1,
+ }, nil
+}
+
+// SchedulerPool 并发安全的时间调度器对象池
+type SchedulerPool struct {
+ schedulers []*Scheduler // 池中维护的时间调度器
+ lock sync.RWMutex // 用于并发安全的锁
+ tick time.Duration // 时间周期
+ wheelSize int64 // 时间轮尺寸
+ size int // 池大小,控制了池中时间调度器的数量
+ generation int64 // 池的代数
+ executor func(name string, caller func()) // 任务执行器
+}
+
+// SetExecutor 设置该事件调度器对象池中整体的任务执行器
+func (p *SchedulerPool) SetExecutor(executor func(name string, caller func())) {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ p.executor = executor
+}
+
+// SetSize 改变时间调度器对象池的大小,当传入的大小小于或等于 0 时,将会返回错误,并且不会发生任何改变
+// - 设置时间调度器对象池的大小可以在运行时动态调整,但是需要注意的是,调整后的大小不会影响已经产生的 Scheduler
+// - 已经产生的 Scheduler 在被释放后将不会回到 SchedulerPool 中
+func (p *SchedulerPool) SetSize(size int) error {
+ if size <= 0 {
+ return fmt.Errorf("scheduler pool size must greater than 0, got: %d", size)
+ }
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ p.size = size
+ return nil
+}
+
+// Get 获取一个特定时间周期及时间轮尺寸的时间调度器,当池中存在可用的时间调度器时,将会直接返回,否则将会创建一个新的时间调度器
+func (p *SchedulerPool) Get() *Scheduler {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ var scheduler *Scheduler
+ if len(p.schedulers) > 0 {
+ scheduler = p.schedulers[0]
+ p.schedulers = p.schedulers[1:]
+ return scheduler
+ }
+ return newScheduler(p, p.tick, p.wheelSize)
+}
+
+// Recycle 释放定时器池的资源并将其重置为全新的状态
+// - 执行该函数后,已有的时间调度器将会被停止,且不会重新加入到池中,一切都是新的开始
+func (p *SchedulerPool) Recycle() {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ for _, scheduler := range p.schedulers {
+ scheduler.wheel.Stop()
+ }
+ p.schedulers = nil
+ p.generation++
+ return
+}
+
+func (p *SchedulerPool) getGeneration() int64 {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+ return p.generation
+}
+
+func (p *SchedulerPool) getExecutor() func(name string, caller func()) {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+ return p.executor
+}
diff --git a/toolkit/chrono/scheduler_task.go b/toolkit/chrono/scheduler_task.go
new file mode 100644
index 0000000..d17fdd4
--- /dev/null
+++ b/toolkit/chrono/scheduler_task.go
@@ -0,0 +1,81 @@
+package chrono
+
+import (
+ "github.com/RussellLuo/timingwheel"
+ "github.com/gorhill/cronexpr"
+ "reflect"
+ "sync"
+ "time"
+)
+
+// schedulerTask 调度器
+type schedulerTask struct {
+ lock sync.RWMutex
+ scheduler *Scheduler // 任务所属的调度器
+ timer *timingwheel.Timer // 任务执行定时器
+ name string // 任务名称
+ after time.Duration // 任务首次执行延迟
+ interval time.Duration // 任务执行间隔
+ function reflect.Value // 任务执行函数
+ args []reflect.Value // 任务执行参数
+ expr *cronexpr.Expression // 任务执行时间表达式
+
+ total int // 任务执行次数
+ trigger int // 任务已执行次数
+ kill bool // 任务是否已关闭
+}
+
+// Name 获取任务名称
+func (t *schedulerTask) Name() string {
+ return t.name
+}
+
+// Next 获取任务下一次执行的时间
+func (t *schedulerTask) Next(prev time.Time) time.Time {
+ t.lock.RLock()
+ defer t.lock.RUnlock()
+
+ if t.kill || (t.expr != nil && t.total > 0 && t.trigger > t.total) {
+ return time.Time{}
+ }
+ if t.expr != nil {
+ next := t.expr.Next(prev)
+ return next
+ }
+ if t.trigger == 0 {
+ t.trigger++
+ return prev.Add(t.after)
+ }
+ t.trigger++
+ return prev.Add(t.interval)
+}
+
+func (t *schedulerTask) caller() {
+ t.lock.Lock()
+
+ if t.kill {
+ t.lock.Unlock()
+ return
+ }
+
+ if t.total > 0 && t.trigger > t.total {
+ t.lock.Unlock()
+ t.scheduler.UnregisterTask(t.name)
+ } else {
+ t.lock.Unlock()
+ }
+ t.function.Call(t.args)
+}
+
+func (t *schedulerTask) close() {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ if t.kill {
+ return
+ }
+ t.kill = true
+ if t.total <= 0 || t.trigger < t.total {
+ t.timer.Stop()
+ }
+}
diff --git a/toolkit/chrono/shceduler_test.go b/toolkit/chrono/shceduler_test.go
new file mode 100644
index 0000000..9ecba82
--- /dev/null
+++ b/toolkit/chrono/shceduler_test.go
@@ -0,0 +1,14 @@
+package chrono_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/chrono"
+ "testing"
+ "time"
+)
+
+func TestRegisterCronTask(t *testing.T) {
+ chrono.RegisterDayMomentTask("newday", time.Now().Add(time.Minute*-2), 0, 0, 0, 0, func() {
+ fmt.Println("newday")
+ })
+}
diff --git a/toolkit/chrono/state_line.go b/toolkit/chrono/state_line.go
new file mode 100644
index 0000000..e0377a4
--- /dev/null
+++ b/toolkit/chrono/state_line.go
@@ -0,0 +1,261 @@
+package chrono
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+ "github.com/kercylan98/minotaur/utils/generic"
+ "strings"
+ "time"
+)
+
+// NewStateLine 创建一个从左向右由早到晚的状态时间线
+func NewStateLine[State generic.Basic](zero State) *StateLine[State] {
+ return &StateLine[State]{
+ states: []State{zero},
+ points: []time.Time{{}},
+ trigger: [][]func(){{}},
+ }
+}
+
+// StateLine 表示一个状态时间线,它记录了一系列时间点及其对应的状态和触发器。
+// 在时间线中,每个时间点都与一个状态和一组触发器相关联,可以通过时间点查找状态,并触发与之相关联的触发器。
+type StateLine[State generic.Basic] struct {
+ states []State // 每个时间点对应的状态
+ points []time.Time // 每个时间点
+ trigger [][]func() // 每个时间点对应的触发器
+}
+
+// Check 根据状态顺序检查时间线是否合法
+// - missingAllowed: 是否允许状态缺失,如果为 true,则状态可以不连续,如果为 false,则状态必须连续
+//
+// 状态不连续表示时间线中存在状态缺失,例如:
+// - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 true,则状态为 [1, 3, 5] 也是合法的
+// - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 false,则状态为 [1, 3, 5] 是不合法的
+func (s *StateLine[State]) Check(missingAllowed bool, states ...State) bool {
+ var indexStored int
+ var indexInput int
+
+ for indexStored < len(s.states) && indexInput < len(states) {
+ if s.states[indexStored] == states[indexInput] {
+ indexStored++
+ indexInput++
+ } else if missingAllowed {
+ indexInput++
+ } else {
+ return false
+ }
+ }
+
+ //如果存储序列还有剩余, 而输入序列已经遍历完
+ if indexStored != len(s.states) && indexInput == len(states) {
+ return false
+ }
+
+ // 如果输入序列还有剩余, 而存储序列已经遍历完
+ if indexStored == len(s.states) && indexInput != len(states) && !missingAllowed {
+ return false
+ }
+
+ return true
+}
+
+// GetMissingStates 获取缺失的状态
+func (s *StateLine[State]) GetMissingStates(states ...State) []State {
+ var missing = make([]State, 0, len(states))
+ for _, state := range states {
+ if !collection.InComparableSlice(s.states, state) {
+ missing = append(missing, state)
+ }
+ }
+ return missing
+}
+
+// HasState 检查时间线中是否包含指定状态
+func (s *StateLine[State]) HasState(state State) bool {
+ return collection.InComparableSlice(s.states, state)
+}
+
+// String 获取时间线的字符串表示
+func (s *StateLine[State]) String() string {
+ var parts []string
+ for i := 0; i < len(s.states); i++ {
+ parts = append(parts, fmt.Sprintf("[%v] %v", s.states[i], s.points[i]))
+ }
+ return strings.Join(parts, " > ")
+}
+
+// AddState 添加一个状态到时间线中,状态不能与任一时间点重合,否则将被忽略
+// - onTrigger: 该状态绑定的触发器,该触发器不会被主动执行,需要主动获取触发器执行
+func (s *StateLine[State]) AddState(state State, t time.Time, onTrigger ...func()) *StateLine[State] {
+ if collection.InComparableSlice(s.states, state) {
+ return s
+ }
+ // 将 t 按照从左到右由早到晚的顺序插入到 points 中
+ for i := 0; i < len(s.points); i++ {
+ if s.points[i].After(t) {
+ s.points = append(s.points[:i], append([]time.Time{t}, s.points[i:]...)...)
+ s.states = append(s.states[:i], append([]State{state}, s.states[i:]...)...)
+ s.trigger = append(s.trigger[:i], append([][]func(){onTrigger}, s.trigger[i:]...)...)
+ return s
+ }
+ }
+ s.points = append(s.points, t)
+ s.states = append(s.states, state)
+ s.trigger = append(s.trigger, onTrigger)
+ return s
+}
+
+// GetTimeByState 获取指定状态的时间点
+func (s *StateLine[State]) GetTimeByState(state State) time.Time {
+ for i := 0; i < len(s.states); i++ {
+ if s.states[i] == state {
+ return s.points[i]
+ }
+ }
+ return time.Time{}
+}
+
+// GetNextTimeByState 获取指定状态的下一个时间点
+func (s *StateLine[State]) GetNextTimeByState(state State) time.Time {
+ for i := 0; i < len(s.states); i++ {
+ if s.states[i] == state && i+1 < len(s.points) {
+ return s.points[i+1]
+ }
+ }
+ return s.points[0]
+}
+
+// GetLastState 获取最后一个状态
+func (s *StateLine[State]) GetLastState() State {
+ return s.states[len(s.states)-1]
+}
+
+// GetPrevTimeByState 获取指定状态的上一个时间点
+func (s *StateLine[State]) GetPrevTimeByState(state State) time.Time {
+ for i := len(s.states) - 1; i >= 0; i-- {
+ if s.states[i] == state && i > 0 {
+ return s.points[i-1]
+ }
+ }
+ return time.Time{}
+}
+
+// GetIndexByState 获取指定状态的索引
+func (s *StateLine[State]) GetIndexByState(state State) int {
+ for i := 0; i < len(s.states); i++ {
+ if s.states[i] == state {
+ return i
+ }
+ }
+ return -1
+}
+
+// GetStateByTime 获取指定时间点的状态
+func (s *StateLine[State]) GetStateByTime(t time.Time) State {
+ for i := len(s.points) - 1; i >= 0; i-- {
+ point := s.points[i]
+ if point.Before(t) || point.Equal(t) {
+ return s.states[i]
+ }
+ }
+ return s.states[len(s.points)-1]
+}
+
+// GetTimeByIndex 获取指定索引的时间点
+func (s *StateLine[State]) GetTimeByIndex(index int) time.Time {
+ return s.points[index]
+}
+
+// Move 时间线整体移动
+func (s *StateLine[State]) Move(d time.Duration) *StateLine[State] {
+ for i := 0; i < len(s.points); i++ {
+ s.points[i] = s.points[i].Add(d)
+ }
+ return s
+}
+
+// GetNextStateTimeByIndex 获取指定索引的下一个时间点
+func (s *StateLine[State]) GetNextStateTimeByIndex(index int) time.Time {
+ return s.points[index+1]
+}
+
+// GetPrevStateTimeByIndex 获取指定索引的上一个时间点
+func (s *StateLine[State]) GetPrevStateTimeByIndex(index int) time.Time {
+ return s.points[index-1]
+}
+
+// GetStateIndexByTime 获取指定时间点的索引
+func (s *StateLine[State]) GetStateIndexByTime(t time.Time) int {
+ for i := len(s.points) - 1; i >= 0; i-- {
+ var point = s.points[i]
+ if point.Before(t) || point.Equal(t) {
+ return i
+ }
+ }
+ return -1
+}
+
+// GetStateCount 获取状态数量
+func (s *StateLine[State]) GetStateCount() int {
+ return len(s.states)
+}
+
+// GetStateByIndex 获取指定索引的状态
+func (s *StateLine[State]) GetStateByIndex(index int) State {
+ return s.states[index]
+}
+
+// GetTriggerByTime 获取指定时间点的触发器
+func (s *StateLine[State]) GetTriggerByTime(t time.Time) []func() {
+ for i := len(s.points) - 1; i >= 0; i-- {
+ var point = s.points[i]
+ if point.Before(t) || point.Equal(t) {
+ return s.trigger[i]
+ }
+ }
+ return nil
+}
+
+// GetTriggerByIndex 获取指定索引的触发器
+func (s *StateLine[State]) GetTriggerByIndex(index int) []func() {
+ return s.trigger[index]
+}
+
+// GetTriggerByState 获取指定状态的触发器
+func (s *StateLine[State]) GetTriggerByState(state State) []func() {
+ for i := 0; i < len(s.states); i++ {
+ if s.states[i] == state {
+ return s.trigger[i]
+ }
+ }
+ return nil
+}
+
+// AddTriggerToState 给指定状态添加触发器
+func (s *StateLine[State]) AddTriggerToState(state State, onTrigger ...func()) *StateLine[State] {
+ for i := 0; i < len(s.states); i++ {
+ if s.states[i] == state {
+ s.trigger[i] = append(s.trigger[i], onTrigger...)
+ return s
+ }
+ }
+ return s
+}
+
+// Iterate 按照时间顺序遍历时间线
+func (s *StateLine[State]) Iterate(handler func(index int, state State, t time.Time) bool) {
+ for i := 0; i < len(s.points); i++ {
+ if !handler(i, s.states[i], s.points[i]) {
+ return
+ }
+ }
+}
+
+// IterateReverse 按照时间逆序遍历时间线
+func (s *StateLine[State]) IterateReverse(handler func(index int, state State, t time.Time) bool) {
+ for i := len(s.points) - 1; i >= 0; i-- {
+ if !handler(i, s.states[i], s.points[i]) {
+ return
+ }
+ }
+}
diff --git a/toolkit/chrono/state_line_test.go b/toolkit/chrono/state_line_test.go
new file mode 100644
index 0000000..df22895
--- /dev/null
+++ b/toolkit/chrono/state_line_test.go
@@ -0,0 +1,20 @@
+package chrono_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/chrono"
+ "testing"
+ "time"
+)
+
+func TestNewStateLine(t *testing.T) {
+ sl := chrono.NewStateLine(0)
+ sl.AddState(1, time.Now())
+ sl.AddState(2, time.Now().Add(-chrono.Hour))
+
+ sl.Iterate(func(index int, state int, ts time.Time) bool {
+ t.Log(index, state, ts)
+ return true
+ })
+
+ t.Log(sl.GetStateByTime(time.Now()))
+}
diff --git a/toolkit/collection/clone.go b/toolkit/collection/clone.go
new file mode 100644
index 0000000..6f1e0ed
--- /dev/null
+++ b/toolkit/collection/clone.go
@@ -0,0 +1,89 @@
+package collection
+
+import (
+ "slices"
+)
+
+// CloneSlice 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片
+func CloneSlice[S ~[]V, V any](slice S) S {
+ return slices.Clone(slice)
+}
+
+// CloneMap 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map
+// - 当 m 为空时,将会返回 nil
+func CloneMap[M ~map[K]V, K comparable, V any](m M) M {
+ if m == nil {
+ return nil
+ }
+
+ var result = make(M, len(m))
+ for k, v := range m {
+ result[k] = v
+ }
+ return result
+}
+
+// CloneSliceN 通过创建一个新切片并将 slice 的元素复制到新切片的方式来克隆切片为 n 个切片
+// - 当 slice 为空时,将会返回 nil,当 n <= 0 时,将会返回零值切片
+func CloneSliceN[S ~[]V, V any](slice S, n int) []S {
+ if slice == nil {
+ return nil
+ }
+ if n <= 0 {
+ return []S{}
+ }
+
+ var result = make([]S, n)
+ for i := 0; i < n; i++ {
+ result[i] = CloneSlice(slice)
+ }
+ return result
+}
+
+// CloneMapN 通过创建一个新 map 并将 m 的元素复制到新 map 的方式来克隆 map 为 n 个 map
+// - 当 m 为空时,将会返回 nil,当 n <= 0 时,将会返回零值切片
+func CloneMapN[M ~map[K]V, K comparable, V any](m M, n int) []M {
+ if m == nil {
+ return nil
+ }
+
+ if n <= 0 {
+ return []M{}
+ }
+
+ var result = make([]M, n)
+ for i := 0; i < n; i++ {
+ result[i] = CloneMap(m)
+ }
+ return result
+}
+
+// CloneSlices 对 slices 中的每一项元素进行克隆,最终返回一个新的二维切片
+// - 当 slices 为空时,将会返回 nil
+// - 该函数相当于使用 CloneSlice 函数一次性对多个切片进行克隆
+func CloneSlices[S ~[]V, V any](slices ...S) []S {
+ if slices == nil {
+ return nil
+ }
+
+ var result = make([]S, len(slices))
+ for i, slice := range slices {
+ result[i] = CloneSlice(slice)
+ }
+ return result
+}
+
+// CloneMaps 对 maps 中的每一项元素进行克隆,最终返回一个新的 map 切片
+// - 当 maps 为空时,将会返回 nil
+// - 该函数相当于使用 CloneMap 函数一次性对多个 map 进行克隆
+func CloneMaps[M ~map[K]V, K comparable, V any](maps ...M) []M {
+ if maps == nil {
+ return nil
+ }
+
+ var result = make([]M, len(maps))
+ for i, m := range maps {
+ result[i] = CloneMap(m)
+ }
+ return result
+}
diff --git a/toolkit/collection/clone_example_test.go b/toolkit/collection/clone_example_test.go
new file mode 100644
index 0000000..7fe9b83
--- /dev/null
+++ b/toolkit/collection/clone_example_test.go
@@ -0,0 +1,70 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+// 在该示例中,将 slice 克隆后将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址
+// - 示例中的结果将会输出 [1 2 3]
+func ExampleCloneSlice() {
+ var slice = []int{1, 2, 3}
+ var result = collection.CloneSlice(slice)
+ fmt.Println(result)
+ // Output:
+ // [1 2 3]
+}
+
+// 在该示例中,将 map 克隆后将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址
+// - 示例中的结果将会输出 3
+func ExampleCloneMap() {
+ var m = map[int]int{1: 1, 2: 2, 3: 3}
+ var result = collection.CloneMap(m)
+ fmt.Println(len(result))
+ // Output:
+ // 3
+}
+
+// 通过将 slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址
+// - result 的结果为 [[1 2 3] [1 2 3]]
+// - 示例中的结果将会输出 2
+func ExampleCloneSliceN() {
+ var slice = []int{1, 2, 3}
+ var result = collection.CloneSliceN(slice, 2)
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
+
+// 通过将 map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址
+// - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对`
+// - 示例中的结果将会输出 2
+func ExampleCloneMapN() {
+ var m = map[int]int{1: 1, 2: 2, 3: 3}
+ var result = collection.CloneMapN(m, 2)
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
+
+// 通过将多个 slice 克隆为 2 个新的 slice,将会得到一个新的 slice result,而 result 和 slice 将不会有任何关联,但是如果 slice 中的元素是引用类型,那么 result 中的元素将会和 slice 中的元素指向同一个地址
+// - result 的结果为 [[1 2 3] [1 2 3]]
+func ExampleCloneSlices() {
+ var slice1 = []int{1, 2, 3}
+ var slice2 = []int{1, 2, 3}
+ var result = collection.CloneSlices(slice1, slice2)
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
+
+// 通过将多个 map 克隆为 2 个新的 map,将会得到一个新的 map result,而 result 和 map 将不会有任何关联,但是如果 map 中的元素是引用类型,那么 result 中的元素将会和 map 中的元素指向同一个地址
+// - result 的结果为 [map[1:1 2:2 3:3] map[1:1 2:2 3:3]] `无序的 Key-Value 对`
+func ExampleCloneMaps() {
+ var m1 = map[int]int{1: 1, 2: 2, 3: 3}
+ var m2 = map[int]int{1: 1, 2: 2, 3: 3}
+ var result = collection.CloneMaps(m1, m2)
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
diff --git a/toolkit/collection/clone_test.go b/toolkit/collection/clone_test.go
new file mode 100644
index 0000000..1fc2803
--- /dev/null
+++ b/toolkit/collection/clone_test.go
@@ -0,0 +1,190 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestCloneSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestCloneSlice_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
+ {"TestCloneSlice_EmptySlice", []int{}, []int{}},
+ {"TestCloneSlice_NilSlice", nil, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.CloneSlice(c.input)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestCloneMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]int
+ }{
+ {"TestCloneMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 1, 2: 2, 3: 3}},
+ {"TestCloneMap_EmptyMap", map[int]int{}, map[int]int{}},
+ {"TestCloneMap_NilMap", nil, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.CloneMap(c.input)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of map is not equal")
+ }
+ for k, v := range actual {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestCloneSliceN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputN int
+ expected [][]int
+ }{
+ {"TestCloneSliceN_NonEmptySlice", []int{1, 2, 3}, 2, [][]int{{1, 2, 3}, {1, 2, 3}}},
+ {"TestCloneSliceN_EmptySlice", []int{}, 2, [][]int{{}, {}}},
+ {"TestCloneSliceN_NilSlice", nil, 2, nil},
+ {"TestCloneSliceN_NonEmptySlice_ZeroN", []int{1, 2, 3}, 0, [][]int{}},
+ {"TestCloneSliceN_EmptySlice_ZeroN", []int{}, 0, [][]int{}},
+ {"TestCloneSliceN_NilSlice_ZeroN", nil, 0, nil},
+ {"TestCloneSliceN_NonEmptySlice_NegativeN", []int{1, 2, 3}, -1, [][]int{}},
+ {"TestCloneSliceN_EmptySlice_NegativeN", []int{}, -1, [][]int{}},
+ {"TestCloneSliceN_NilSlice_NegativeN", nil, -1, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.CloneSliceN(c.input, c.inputN)
+ if actual == nil {
+ if c.expected != nil {
+ t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, c.inputN, actual, "after clone, the expected is nil")
+ }
+ return
+ }
+ for a, i := range actual {
+ for b, v := range i {
+ if v != c.expected[a][b] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, c.inputN, actual, "after clone, the inputV of input is not equal")
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestCloneMapN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ inputN int
+ expected []map[int]int
+ }{
+ {"TestCloneMapN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 2, []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}},
+ {"TestCloneMapN_EmptyMap", map[int]int{}, 2, []map[int]int{{}, {}}},
+ {"TestCloneMapN_NilMap", nil, 2, nil},
+ {"TestCloneMapN_NonEmptyMap_ZeroN", map[int]int{1: 1, 2: 2, 3: 3}, 0, []map[int]int{}},
+ {"TestCloneMapN_EmptyMap_ZeroN", map[int]int{}, 0, []map[int]int{}},
+ {"TestCloneMapN_NilMap_ZeroN", nil, 0, nil},
+ {"TestCloneMapN_NonEmptyMap_NegativeN", map[int]int{1: 1, 2: 2, 3: 3}, -1, []map[int]int{}},
+ {"TestCloneMapN_EmptyMap_NegativeN", map[int]int{}, -1, []map[int]int{}},
+ {"TestCloneMapN_NilMap_NegativeN", nil, -1, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.CloneMapN(c.input, c.inputN)
+ if actual == nil {
+ if c.expected != nil {
+ t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, actual, c.inputN, "after clone, the expected is nil")
+ }
+ return
+ }
+ for a, i := range actual {
+ for b, v := range i {
+ if v != c.expected[a][b] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, actual, c.inputN, "after clone, the inputV of map is not equal")
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestCloneSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input [][]int
+ expected [][]int
+ }{
+ {"TestCloneSlices_NonEmptySlices", [][]int{{1, 2, 3}, {1, 2, 3}}, [][]int{{1, 2, 3}, {1, 2, 3}}},
+ {"TestCloneSlices_EmptySlices", [][]int{{}, {}}, [][]int{{}, {}}},
+ {"TestCloneSlices_NilSlices", nil, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.CloneSlices(c.input...)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ for a, i := range actual {
+ for b, v := range i {
+ if v != c.expected[a][b] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of input is not equal")
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestCloneMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []map[int]int
+ expected []map[int]int
+ }{
+ {"TestCloneMaps_NonEmptyMaps", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}, []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}},
+ {"TestCloneMaps_EmptyMaps", []map[int]int{{}, {}}, []map[int]int{{}, {}}},
+ {"TestCloneMaps_NilMaps", nil, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.CloneMaps(c.input...)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of maps is not equal")
+ }
+ for a, i := range actual {
+ for b, v := range i {
+ if v != c.expected[a][b] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of maps is not equal")
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/collection.go b/toolkit/collection/collection.go
new file mode 100644
index 0000000..519b27d
--- /dev/null
+++ b/toolkit/collection/collection.go
@@ -0,0 +1,10 @@
+package collection
+
+import "github.com/kercylan98/minotaur/utils/generic"
+
+// ComparisonHandler 用于比较 `source` 和 `target` 两个值是否相同的比较函数
+// - 该函数接受两个参数,分别是源值和目标值,返回 true 的情况下即表示两者相同
+type ComparisonHandler[V any] func(source, target V) bool
+
+// OrderedValueGetter 用于获取 v 的可排序字段值的函数
+type OrderedValueGetter[V any, N generic.Ordered] func(v V) N
diff --git a/toolkit/collection/contains.go b/toolkit/collection/contains.go
new file mode 100644
index 0000000..dda4939
--- /dev/null
+++ b/toolkit/collection/contains.go
@@ -0,0 +1,385 @@
+package collection
+
+// EqualSlice 检查两个切片是否相等,当 handler 返回 true 时,表示 slice1 中的某个元素和 slice2 中的某个元素相匹配
+// - 当两个切片的容量不同时,不会影响最终的比较结果
+func EqualSlice[S ~[]V, V any](slice1 S, slice2 S, handler ComparisonHandler[V]) bool {
+ if len(slice1) != len(slice2) {
+ return false
+ }
+ for i, v1 := range slice1 {
+ if !handler(v1, slice2[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualComparableSlice 检查两个切片的值是否相同
+// - 当两个切片的容量不同时,不会影响最终的比较结果
+func EqualComparableSlice[S ~[]V, V comparable](slice1 S, slice2 S) bool {
+ if len(slice1) != len(slice2) {
+ return false
+ }
+ for i, v1 := range slice1 {
+ if v1 != slice2[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualMap 检查两个 map 是否相等,当 handler 返回 true 时,表示 map1 中的某个元素和 map2 中的某个元素相匹配
+// - 当两个 map 的容量不同时,不会影响最终的比较结果
+func EqualMap[M ~map[K]V, K comparable, V any](map1 M, map2 M, handler ComparisonHandler[V]) bool {
+ if len(map1) != len(map2) {
+ return false
+ }
+ for k, v1 := range map1 {
+ if !handler(v1, map2[k]) {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualComparableMap 检查两个 map 的值是否相同
+// - 当两个 map 的容量不同时,不会影响最终的比较结果
+func EqualComparableMap[M ~map[K]V, K comparable, V comparable](map1 M, map2 M) bool {
+ if len(map1) != len(map2) {
+ return false
+ }
+ for k, v1 := range map1 {
+ if v1 != map2[k] {
+ return false
+ }
+ }
+ return true
+}
+
+// InSlice 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配
+func InSlice[S ~[]V, V any](slice S, v V, handler ComparisonHandler[V]) bool {
+ if len(slice) == 0 {
+ return false
+ }
+ for _, value := range slice {
+ if handler(v, value) {
+ return true
+ }
+ }
+ return false
+}
+
+// InComparableSlice 检查 v 是否被包含在 slice 中
+func InComparableSlice[S ~[]V, V comparable](slice S, v V) bool {
+ if slice == nil {
+ return false
+ }
+ for _, value := range slice {
+ if value == v {
+ return true
+ }
+ }
+ return false
+}
+
+// AllInSlice 检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配
+// - 在所有 values 中的元素都被包含在 slice 中时,返回 true
+// - 当 values 长度为 0 或为 nil 时,将返回 true
+func AllInSlice[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool {
+ if len(slice) == 0 {
+ return false
+ }
+ for _, value := range values {
+ if !InSlice(slice, value, handler) {
+ return false
+ }
+ }
+ return true
+}
+
+// AllInComparableSlice 检查 values 中的所有元素是否均被包含在 slice 中
+// - 在所有 values 中的元素都被包含在 slice 中时,返回 true
+// - 当 values 长度为 0 或为 nil 时,将返回 true
+func AllInComparableSlice[S ~[]V, V comparable](slice S, values []V) bool {
+ if len(slice) == 0 {
+ return false
+ }
+ for _, value := range values {
+ if !InComparableSlice(slice, value) {
+ return false
+ }
+ }
+ return true
+}
+
+// AnyInSlice 检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配
+// - 当 values 中的任意一个元素被包含在 slice 中时,返回 true
+func AnyInSlice[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool {
+ if len(slice) == 0 {
+ return false
+ }
+ for _, value := range values {
+ if InSlice(slice, value, handler) {
+ return true
+ }
+ }
+ return false
+}
+
+// AnyInComparableSlice 检查 values 中的任意一个元素是否被包含在 slice 中
+// - 当 values 中的任意一个元素被包含在 slice 中时,返回 true
+func AnyInComparableSlice[S ~[]V, V comparable](slice S, values []V) bool {
+ if len(slice) == 0 {
+ return false
+ }
+ for _, value := range values {
+ if InComparableSlice(slice, value) {
+ return true
+ }
+ }
+ return false
+}
+
+// InSlices 通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配
+// - 当传入的 v 被包含在 slices 的任一成员中时,返回 true
+func InSlices[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool {
+ return InSlice(MergeSlices(slices...), v, handler)
+}
+
+// InComparableSlices 通过将多个切片合并后检查 v 是否被包含在 slices 中
+// - 当传入的 v 被包含在 slices 的任一成员中时,返回 true
+func InComparableSlices[S ~[]V, V comparable](slices []S, v V) bool {
+ return InComparableSlice(MergeSlices(slices...), v)
+}
+
+// AllInSlices 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配
+// - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true
+func AllInSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool {
+ return AllInSlice(MergeSlices(slices...), values, handler)
+}
+
+// AllInComparableSlices 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中
+// - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true
+func AllInComparableSlices[S ~[]V, V comparable](slices []S, values []V) bool {
+ return AllInComparableSlice(MergeSlices(slices...), values)
+}
+
+// AnyInSlices 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配
+// - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true
+func AnyInSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool {
+ return AnyInSlice(MergeSlices(slices...), values, handler)
+}
+
+// AnyInComparableSlices 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中
+// - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true
+func AnyInComparableSlices[S ~[]V, V comparable](slices []S, values []V) bool {
+ return AnyInComparableSlice(MergeSlices(slices...), values)
+}
+
+// InAllSlices 检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配
+// - 当 v 被包含在 slices 的每一项元素中时,返回 true
+func InAllSlices[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool {
+ if len(slices) == 0 {
+ return false
+ }
+ for _, slice := range slices {
+ if !InSlice(slice, v, handler) {
+ return false
+ }
+ }
+ return true
+}
+
+// InAllComparableSlices 检查 v 是否被包含在 slices 的每一项元素中
+// - 当 v 被包含在 slices 的每一项元素中时,返回 true
+func InAllComparableSlices[S ~[]V, V comparable](slices []S, v V) bool {
+ if len(slices) == 0 {
+ return false
+ }
+ for _, slice := range slices {
+ if !InComparableSlice(slice, v) {
+ return false
+ }
+ }
+ return true
+}
+
+// AnyInAllSlices 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配
+// - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true
+func AnyInAllSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool {
+ if len(slices) == 0 {
+ return false
+ }
+ for _, slice := range slices {
+ if !AnyInSlice(slice, values, handler) {
+ return false
+ }
+ }
+ return true
+}
+
+// AnyInAllComparableSlices 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素
+// - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true
+func AnyInAllComparableSlices[S ~[]V, V comparable](slices []S, values []V) bool {
+ if len(slices) == 0 {
+ return false
+ }
+ for _, slice := range slices {
+ if !AnyInComparableSlice(slice, values) {
+ return false
+ }
+ }
+ return true
+}
+
+// KeyInMap 检查 m 中是否包含特定 key
+func KeyInMap[M ~map[K]V, K comparable, V any](m M, key K) bool {
+ _, ok := m[key]
+ return ok
+}
+
+// ValueInMap 检查 m 中是否包含特定 value,当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配
+func ValueInMap[M ~map[K]V, K comparable, V any](m M, value V, handler ComparisonHandler[V]) bool {
+ if len(m) == 0 {
+ return false
+ }
+ for _, v := range m {
+ if handler(value, v) {
+ return true
+ }
+ }
+ return false
+}
+
+// AllKeyInMap 检查 m 中是否包含 keys 中所有的元素
+func AllKeyInMap[M ~map[K]V, K comparable, V any](m M, keys ...K) bool {
+ if len(m) < len(keys) {
+ return false
+ }
+ for _, key := range keys {
+ if !KeyInMap(m, key) {
+ return false
+ }
+ }
+ return true
+}
+
+// AllValueInMap 检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配
+func AllValueInMap[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) bool {
+ if len(m) == 0 {
+ return false
+ }
+ for _, value := range values {
+ if !ValueInMap(m, value, handler) {
+ return false
+ }
+ }
+ return true
+}
+
+// AnyKeyInMap 检查 m 中是否包含 keys 中任意一个元素
+func AnyKeyInMap[M ~map[K]V, K comparable, V any](m M, keys ...K) bool {
+ if len(m) == 0 {
+ return false
+ }
+ for _, key := range keys {
+ if KeyInMap(m, key) {
+ return true
+ }
+ }
+ return false
+}
+
+// AnyValueInMap 检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配
+func AnyValueInMap[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) bool {
+ if len(m) == 0 {
+ return false
+ }
+ for _, value := range values {
+ if ValueInMap(m, value, handler) {
+ return true
+ }
+ }
+ return false
+}
+
+// AllKeyInMaps 检查 maps 中的每一个元素是否均包含 keys 中所有的元素
+func AllKeyInMaps[M ~map[K]V, K comparable, V any](maps []M, keys ...K) bool {
+ if len(maps) == 0 {
+ return false
+ }
+ for _, m := range maps {
+ if !AllKeyInMap(m, keys...) {
+ return false
+ }
+ }
+ return true
+}
+
+// AllValueInMaps 检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配
+func AllValueInMaps[M ~map[K]V, K comparable, V any](maps []M, values []V, handler ComparisonHandler[V]) bool {
+ if len(maps) == 0 {
+ return false
+ }
+ for _, m := range maps {
+ if !AllValueInMap(m, values, handler) {
+ return false
+ }
+ }
+ return true
+}
+
+// AnyKeyInMaps 检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中
+// - 当 keys 中的任意一个元素被包含在 maps 中的任意一个元素中时,返回 true
+func AnyKeyInMaps[M ~map[K]V, K comparable, V any](maps []M, keys ...K) bool {
+ if len(maps) == 0 {
+ return false
+ }
+ for _, m := range maps {
+ if AnyKeyInMap(m, keys...) {
+ return true
+ }
+ }
+ return false
+}
+
+// AnyValueInMaps 检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配
+// - 当 maps 中的任意一个元素包含 value 中的任意一个元素时,返回 true
+func AnyValueInMaps[M ~map[K]V, K comparable, V any](maps []M, values []V, handler ComparisonHandler[V]) bool {
+ if len(maps) == 0 {
+ return false
+ }
+ for _, m := range maps {
+ if !AnyValueInMap(m, values, handler) {
+ return false
+ }
+ }
+ return true
+}
+
+// KeyInAllMaps 检查 key 是否被包含在 maps 的每一个元素中
+func KeyInAllMaps[M ~map[K]V, K comparable, V any](maps []M, key K) bool {
+ if len(maps) == 0 {
+ return false
+ }
+ for _, m := range maps {
+ if !KeyInMap(m, key) {
+ return false
+ }
+ }
+ return true
+}
+
+// AnyKeyInAllMaps 检查 maps 中的每一个元素是否均包含 keys 中任意一个元素
+// - 当 maps 中的每一个元素均包含 keys 中任意一个元素时,返回 true
+func AnyKeyInAllMaps[M ~map[K]V, K comparable, V any](maps []M, keys []K) bool {
+ if len(maps) == 0 {
+ return false
+ }
+ for _, m := range maps {
+ if !AnyKeyInMap(m, keys...) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/toolkit/collection/contains_example_test.go b/toolkit/collection/contains_example_test.go
new file mode 100644
index 0000000..033f0fe
--- /dev/null
+++ b/toolkit/collection/contains_example_test.go
@@ -0,0 +1,280 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleEqualSlice() {
+ s1 := []int{1, 2, 3}
+ s2 := []int{1}
+ s3 := []int{1, 2, 3}
+ fmt.Println(collection.EqualSlice(s1, s2, func(source, target int) bool {
+ return source == target
+ }))
+ fmt.Println(collection.EqualSlice(s1, s3, func(source, target int) bool {
+ return source == target
+ }))
+ // Output:
+ // false
+ // true
+}
+
+func ExampleEqualComparableSlice() {
+ s1 := []int{1, 2, 3}
+ s2 := []int{1}
+ s3 := []int{1, 2, 3}
+ fmt.Println(collection.EqualComparableSlice(s1, s2))
+ fmt.Println(collection.EqualComparableSlice(s1, s3))
+ // Output:
+ // false
+ // true
+}
+
+func ExampleEqualMap() {
+ m1 := map[string]int{"a": 1, "b": 2}
+ m2 := map[string]int{"a": 1}
+ m3 := map[string]int{"a": 1, "b": 2}
+ fmt.Println(collection.EqualMap(m1, m2, func(source, target int) bool {
+ return source == target
+ }))
+ fmt.Println(collection.EqualMap(m1, m3, func(source, target int) bool {
+ return source == target
+ }))
+ // Output:
+ // false
+ // true
+}
+
+func ExampleEqualComparableMap() {
+ m1 := map[string]int{"a": 1, "b": 2}
+ m2 := map[string]int{"a": 1}
+ m3 := map[string]int{"a": 1, "b": 2}
+ fmt.Println(collection.EqualComparableMap(m1, m2))
+ fmt.Println(collection.EqualComparableMap(m1, m3))
+ // Output:
+ // false
+ // true
+}
+
+func ExampleInSlice() {
+ result := collection.InSlice([]int{1, 2, 3}, 2, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleInComparableSlice() {
+ result := collection.InComparableSlice([]int{1, 2, 3}, 2)
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllInSlice() {
+ result := collection.AllInSlice([]int{1, 2, 3}, []int{1, 2}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllInComparableSlice() {
+ result := collection.AllInComparableSlice([]int{1, 2, 3}, []int{1, 2})
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyInSlice() {
+ result := collection.AnyInSlice([]int{1, 2, 3}, []int{1, 2}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyInComparableSlice() {
+ result := collection.AnyInComparableSlice([]int{1, 2, 3}, []int{1, 2})
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleInSlices() {
+ result := collection.InSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleInComparableSlices() {
+ result := collection.InComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2)
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllInSlices() {
+ result := collection.AllInSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllInComparableSlices() {
+ result := collection.AllInComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2})
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyInSlices() {
+ result := collection.AnyInSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyInComparableSlices() {
+ result := collection.AnyInComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2})
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleInAllSlices() {
+ result := collection.InAllSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // false
+}
+
+func ExampleInAllComparableSlices() {
+ result := collection.InAllComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2)
+ fmt.Println(result)
+ // Output:
+ // false
+}
+
+func ExampleAnyInAllSlices() {
+ result := collection.AnyInAllSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // false
+}
+
+func ExampleAnyInAllComparableSlices() {
+ result := collection.AnyInAllComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2})
+ fmt.Println(result)
+ // Output:
+ // false
+}
+
+func ExampleKeyInMap() {
+ result := collection.KeyInMap(map[string]int{"a": 1, "b": 2}, "a")
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleValueInMap() {
+ result := collection.ValueInMap(map[string]int{"a": 1, "b": 2}, 2, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllKeyInMap() {
+ result := collection.AllKeyInMap(map[string]int{"a": 1, "b": 2}, "a", "b")
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllValueInMap() {
+ result := collection.AllValueInMap(map[string]int{"a": 1, "b": 2}, []int{1}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyKeyInMap() {
+ result := collection.AnyKeyInMap(map[string]int{"a": 1, "b": 2}, "a", "b")
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyValueInMap() {
+ result := collection.AnyValueInMap(map[string]int{"a": 1, "b": 2}, []int{1}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllKeyInMaps() {
+ result := collection.AllKeyInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a", "b")
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAllValueInMaps() {
+ result := collection.AllValueInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []int{1}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyKeyInMaps() {
+ result := collection.AnyKeyInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a", "b")
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyValueInMaps() {
+ result := collection.AnyValueInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []int{1}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleKeyInAllMaps() {
+ result := collection.KeyInAllMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a")
+ fmt.Println(result)
+ // Output:
+ // true
+}
+
+func ExampleAnyKeyInAllMaps() {
+ result := collection.AnyKeyInAllMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []string{"a"})
+ fmt.Println(result)
+ // Output:
+ // true
+}
diff --git a/toolkit/collection/contains_test.go b/toolkit/collection/contains_test.go
new file mode 100644
index 0000000..f9f1b32
--- /dev/null
+++ b/toolkit/collection/contains_test.go
@@ -0,0 +1,809 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+var intComparisonHandler = func(source, target int) bool {
+ return source == target
+}
+
+func TestEqualSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV []int
+ expected bool
+ }{
+ {"TestEqualSlice_NonEmptySliceEqual", []int{1, 2, 3}, []int{1, 2, 3}, true},
+ {"TestEqualSlice_NonEmptySliceNotEqual", []int{1, 2, 3}, []int{1, 2}, false},
+ {"TestEqualSlice_EmptySlice", []int{}, []int{}, true},
+ {"TestEqualSlice_NilSlice", nil, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.EqualSlice(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, "not as expected")
+ }
+ })
+ }
+}
+
+func TestEqualComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV []int
+ expected bool
+ }{
+ {"TestEqualComparableSlice_NonEmptySliceEqual", []int{1, 2, 3}, []int{1, 2, 3}, true},
+ {"TestEqualComparableSlice_NonEmptySliceNotEqual", []int{1, 2, 3}, []int{1, 2}, false},
+ {"TestEqualComparableSlice_EmptySlice", []int{}, []int{}, true},
+ {"TestEqualComparableSlice_NilSlice", nil, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.EqualComparableSlice(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 TestEqualMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ inputV map[int]int
+ expected bool
+ }{
+ {"TestEqualMap_NonEmptyMapEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}, true},
+ {"TestEqualMap_NonEmptyMapNotEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1}, false},
+ {"TestEqualMap_EmptyMap", map[int]int{}, map[int]int{}, true},
+ {"TestEqualMap_NilMap", nil, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.EqualMap(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, "not as expected")
+ }
+ })
+ }
+}
+
+func TestEqualComparableMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ inputV map[int]int
+ expected bool
+ }{
+ {"TestEqualComparableMap_NonEmptyMapEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}, true},
+ {"TestEqualComparableMap_NonEmptyMapNotEqual", map[int]int{1: 1, 2: 2}, map[int]int{1: 1}, false},
+ {"TestEqualComparableMap_EmptyMap", map[int]int{}, map[int]int{}, true},
+ {"TestEqualComparableMap_NilMap", nil, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.EqualComparableMap(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 TestInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV int
+ expected bool
+ }{
+ {"TestInSlice_NonEmptySliceIn", []int{1, 2, 3}, 1, true},
+ {"TestInSlice_NonEmptySliceNotIn", []int{1, 2, 3}, 4, false},
+ {"TestInSlice_EmptySlice", []int{}, 1, false},
+ {"TestInSlice_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.InSlice(c.input, c.inputV, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV int
+ expected bool
+ }{
+ {"TestInComparableSlice_NonEmptySliceIn", []int{1, 2, 3}, 1, true},
+ {"TestInComparableSlice_NonEmptySliceNotIn", []int{1, 2, 3}, 4, false},
+ {"TestInComparableSlice_EmptySlice", []int{}, 1, false},
+ {"TestInComparableSlice_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.InComparableSlice(c.input, c.inputV)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAllInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV []int
+ expected bool
+ }{
+ {"TestAllInSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
+ {"TestAllInSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, false},
+ {"TestAllInSlice_EmptySlice", []int{}, []int{1, 2}, false},
+ {"TestAllInSlice_NilSlice", nil, []int{1, 2}, false},
+ {"TestAllInSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, true},
+ {"TestAllInSlice_NilValueSlice", []int{1, 2, 3}, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllInSlice(c.input, c.inputV, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAllInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV []int
+ expected bool
+ }{
+ {"TestAllInComparableSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
+ {"TestAllInComparableSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, false},
+ {"TestAllInComparableSlice_EmptySlice", []int{}, []int{1, 2}, false},
+ {"TestAllInComparableSlice_NilSlice", nil, []int{1, 2}, false},
+ {"TestAllInComparableSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, true},
+ {"TestAllInComparableSlice_NilValueSlice", []int{1, 2, 3}, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllInComparableSlice(c.input, c.inputV)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAnyInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV []int
+ expected bool
+ }{
+ {"TestAnyInSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
+ {"TestAnyInSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, true},
+ {"TestAnyInSlice_EmptySlice", []int{}, []int{1, 2}, false},
+ {"TestAnyInSlice_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyInSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, false},
+ {"TestAnyInSlice_NilValueSlice", []int{1, 2, 3}, nil, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyInSlice(c.input, c.inputV, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAnyInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ inputV []int
+ expected bool
+ }{
+ {"TestAnyInComparableSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
+ {"TestAnyInComparableSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, true},
+ {"TestAnyInComparableSlice_EmptySlice", []int{}, []int{1, 2}, false},
+ {"TestAnyInComparableSlice_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyInComparableSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, false},
+ {"TestAnyInComparableSlice_NilValueSlice", []int{1, 2, 3}, nil, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyInComparableSlice(c.input, c.inputV)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestInSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input [][]int
+ inputV int
+ expected bool
+ }{
+ {"TestInSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, 1, true},
+ {"TestInSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
+ {"TestInSlices_EmptySlice", [][]int{{}, {}}, 1, false},
+ {"TestInSlices_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.InSlices(c.input, c.inputV, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestInComparableSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input [][]int
+ inputV int
+ expected bool
+ }{
+ {"TestInComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, 1, true},
+ {"TestInComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
+ {"TestInComparableSlices_EmptySlice", [][]int{{}, {}}, 1, false},
+ {"TestInComparableSlices_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.InComparableSlices(c.input, c.inputV)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
+ }
+ })
+ }
+}
+
+func TestAllInSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input [][]int
+ inputValues []int
+ expected bool
+ }{
+ {"TestAllInSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
+ {"TestAllInSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
+ {"TestAllInSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
+ {"TestAllInSlices_NilSlice", nil, []int{1, 2}, false},
+ {"TestAllInSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, true},
+ {"TestAllInSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllInSlices(c.input, c.inputValues, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAllInComparableSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input [][]int
+ inputValues []int
+ expected bool
+ }{
+ {"TestAllInComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
+ {"TestAllInComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
+ {"TestAllInComparableSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
+ {"TestAllInComparableSlices_NilSlice", nil, []int{1, 2}, false},
+ {"TestAllInComparableSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, true},
+ {"TestAllInComparableSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllInComparableSlices(c.input, c.inputValues)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
+ }
+ })
+ }
+}
+
+func TestAnyInSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ slices [][]int
+ values []int
+ expected bool
+ }{
+ {"TestAnyInSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
+ {"TestAnyInSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, true},
+ {"TestAnyInSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyInSlices_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyInSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
+ {"TestAnyInSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyInSlices(c.slices, c.values, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAnyInComparableSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ slices [][]int
+ values []int
+ expected bool
+ }{
+ {"TestAnyInComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
+ {"TestAnyInComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, true},
+ {"TestAnyInComparableSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyInComparableSlices_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyInComparableSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
+ {"TestAnyInComparableSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyInComparableSlices(c.slices, c.values)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
+ }
+ })
+ }
+}
+
+func TestInAllSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ slices [][]int
+ value int
+ expected bool
+ }{
+ {"TestInAllSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, 1, true},
+ {"TestInAllSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
+ {"TestInAllSlices_EmptySlice", [][]int{{}, {}}, 1, false},
+ {"TestInAllSlices_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.InAllSlices(c.slices, c.value, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestInAllComparableSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ slices [][]int
+ value int
+ expected bool
+ }{
+ {"TestInAllComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, 1, true},
+ {"TestInAllComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
+ {"TestInAllComparableSlices_EmptySlice", [][]int{{}, {}}, 1, false},
+ {"TestInAllComparableSlices_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.InAllComparableSlices(c.slices, c.value)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
+ }
+ })
+ }
+}
+
+func TestAnyInAllSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ slices [][]int
+ values []int
+ expected bool
+ }{
+ {"TestAnyInAllSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, []int{1, 2}, true},
+ {"TestAnyInAllSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
+ {"TestAnyInAllSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyInAllSlices_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyInAllSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
+ {"TestAnyInAllSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyInAllSlices(c.slices, c.values, func(source, target int) bool {
+ return source == target
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAnyInAllComparableSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ slices [][]int
+ values []int
+ expected bool
+ }{
+ {"TestAnyInAllComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, []int{1, 2}, true},
+ {"TestAnyInAllComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
+ {"TestAnyInAllComparableSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyInAllComparableSlices_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyInAllComparableSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
+ {"TestAnyInAllComparableSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyInAllComparableSlices(c.slices, c.values)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
+ }
+ })
+ }
+}
+
+func TestKeyInMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ m map[int]int
+ key int
+ expected bool
+ }{
+ {"TestKeyInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, 1, true},
+ {"TestKeyInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, 3, false},
+ {"TestKeyInMap_EmptySlice", map[int]int{}, 1, false},
+ {"TestKeyInMap_NilSlice", nil, 1, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.KeyInMap(c.m, c.key)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestValueInMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ m map[int]int
+ value int
+ handler collection.ComparisonHandler[int]
+ expected bool
+ }{
+ {"TestValueInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, 1, intComparisonHandler, true},
+ {"TestValueInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, 3, intComparisonHandler, false},
+ {"TestValueInMap_EmptySlice", map[int]int{}, 1, intComparisonHandler, false},
+ {"TestValueInMap_NilSlice", nil, 1, intComparisonHandler, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.ValueInMap(c.m, c.value, c.handler)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAllKeyInMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ m map[int]int
+ keys []int
+ expected bool
+ }{
+ {"TestAllKeyInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
+ {"TestAllKeyInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, false},
+ {"TestAllKeyInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
+ {"TestAllKeyInMap_NilSlice", nil, []int{1, 2}, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllKeyInMap(c.m, c.keys...)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAllValueInMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ m map[int]int
+ values []int
+ expected bool
+ }{
+ {"TestAllValueInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
+ {"TestAllValueInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, false},
+ {"TestAllValueInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
+ {"TestAllValueInMap_NilSlice", nil, []int{1, 2}, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllValueInMap(c.m, c.values, intComparisonHandler)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestAnyKeyInMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ m map[int]int
+ keys []int
+ expected bool
+ }{
+ {"TestAnyKeyInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
+ {"TestAnyKeyInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, true},
+ {"TestAnyKeyInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
+ {"TestAnyKeyInMap_NilSlice", nil, []int{1, 2}, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyKeyInMap(c.m, c.keys...)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAnyValueInMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ m map[int]int
+ values []int
+ expected bool
+ }{
+ {"TestAnyValueInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
+ {"TestAnyValueInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, true},
+ {"TestAnyValueInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
+ {"TestAnyValueInMap_NilSlice", nil, []int{1, 2}, false},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyValueInMap(c.m, c.values, intComparisonHandler)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAllKeyInMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ maps []map[int]int
+ keys []int
+ expected bool
+ }{
+ {"TestAllKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
+ {"TestAllKeyInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, false},
+ {"TestAllKeyInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
+ {"TestAllKeyInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
+ {"TestAllKeyInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
+ {"TestAllKeyInMaps_NilSlice", nil, []int{1, 2}, false},
+ {"TestAllKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllKeyInMaps(c.maps, c.keys...)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAllValueInMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ maps []map[int]int
+ values []int
+ expected bool
+ }{
+ {"TestAllValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
+ {"TestAllValueInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, false},
+ {"TestAllValueInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
+ {"TestAllValueInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
+ {"TestAllValueInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
+ {"TestAllValueInMaps_NilSlice", nil, []int{1, 2}, false},
+ {"TestAllValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AllValueInMaps(c.maps, c.values, intComparisonHandler)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAnyKeyInMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ maps []map[int]int
+ keys []int
+ expected bool
+ }{
+ {"TestAnyKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, true},
+ {"TestAnyKeyInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, true},
+ {"TestAnyKeyInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, true},
+ {"TestAnyKeyInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyKeyInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
+ {"TestAnyKeyInMaps_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyKeyInMaps(c.maps, c.keys...)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAnyValueInMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ maps []map[int]int
+ values []int
+ expected bool
+ }{
+ {"TestAnyValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
+ {"TestAnyValueInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, true},
+ {"TestAnyValueInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
+ {"TestAnyValueInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyValueInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
+ {"TestAnyValueInMaps_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyValueInMaps(c.maps, c.values, intComparisonHandler)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestKeyInAllMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ maps []map[int]int
+ key int
+ expected bool
+ }{
+ {"TestKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, 1, false},
+ {"TestKeyInAllMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, 3, false},
+ {"TestKeyInAllMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, 1, false},
+ {"TestKeyInAllMaps_NilSlice", []map[int]int{{}, {}}, 1, false},
+ {"TestKeyInAllMaps_EmptySlice", []map[int]int{}, 1, false},
+ {"TestKeyInAllMaps_NilSlice", nil, 1, false},
+ {"TestKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, 1, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.KeyInAllMaps(c.maps, c.key)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
+ }
+ })
+ }
+}
+
+func TestAnyKeyInAllMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ maps []map[int]int
+ keys []int
+ expected bool
+ }{
+ {"TestAnyKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
+ {"TestAnyKeyInAllMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, true},
+ {"TestAnyKeyInAllMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
+ {"TestAnyKeyInAllMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
+ {"TestAnyKeyInAllMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
+ {"TestAnyKeyInAllMaps_NilSlice", nil, []int{1, 2}, false},
+ {"TestAnyKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var actual = collection.AnyKeyInAllMaps(c.maps, c.keys)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, actual)
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/convert.go b/toolkit/collection/convert.go
new file mode 100644
index 0000000..e5e7f54
--- /dev/null
+++ b/toolkit/collection/convert.go
@@ -0,0 +1,171 @@
+package collection
+
+// ConvertSliceToBatches 将切片 s 转换为分批次的切片,当 batchSize 小于等于 0 或者 s 长度为 0 时,将会返回 nil
+func ConvertSliceToBatches[S ~[]V, V any](s S, batchSize int) []S {
+ if len(s) == 0 || batchSize <= 0 {
+ return nil
+ }
+ var batches = make([]S, 0, len(s)/batchSize+1)
+ for i := 0; i < len(s); i += batchSize {
+ var end = i + batchSize
+ if end > len(s) {
+ end = len(s)
+ }
+ batches = append(batches, s[i:end])
+ }
+ return batches
+}
+
+// ConvertMapKeysToBatches 将映射的键转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil
+func ConvertMapKeysToBatches[M ~map[K]V, K comparable, V any](m M, batchSize int) [][]K {
+ if len(m) == 0 || batchSize <= 0 {
+ return nil
+ }
+ var batches = make([][]K, 0, len(m)/batchSize+1)
+ var keys = ConvertMapKeysToSlice(m)
+ for i := 0; i < len(keys); i += batchSize {
+ var end = i + batchSize
+ if end > len(keys) {
+ end = len(keys)
+ }
+ batches = append(batches, keys[i:end])
+ }
+ return batches
+}
+
+// ConvertMapValuesToBatches 将映射的值转换为分批次的切片,当 batchSize 小于等于 0 或者 m 长度为 0 时,将会返回 nil
+func ConvertMapValuesToBatches[M ~map[K]V, K comparable, V any](m M, batchSize int) [][]V {
+ if len(m) == 0 || batchSize <= 0 {
+ return nil
+ }
+ var batches = make([][]V, 0, len(m)/batchSize+1)
+ var values = ConvertMapValuesToSlice(m)
+ for i := 0; i < len(values); i += batchSize {
+ var end = i + batchSize
+ if end > len(values) {
+ end = len(values)
+ }
+ batches = append(batches, values[i:end])
+ }
+ return batches
+}
+
+// ConvertSliceToAny 将切片转换为任意类型的切片
+func ConvertSliceToAny[S ~[]V, V any](s S) []any {
+ if len(s) == 0 {
+ return nil
+ }
+ var r = make([]any, len(s))
+ for i, v := range s {
+ r[i] = v
+ }
+ return r
+}
+
+// ConvertSliceToIndexMap 将切片转换为索引为键的映射
+func ConvertSliceToIndexMap[S ~[]V, V any](s S) map[int]V {
+ if len(s) == 0 {
+ return make(map[int]V)
+ }
+ var r = make(map[int]V, len(s))
+ for i, v := range s {
+ r[i] = v
+ }
+ return r
+}
+
+// ConvertSliceToIndexOnlyMap 将切片转换为索引为键的映射
+func ConvertSliceToIndexOnlyMap[S ~[]V, V any](s S) map[int]struct{} {
+ if len(s) == 0 {
+ return nil
+ }
+ var r = make(map[int]struct{}, len(s))
+ for i := range s {
+ r[i] = struct{}{}
+ }
+ return r
+}
+
+// ConvertSliceToMap 将切片转换为值为键的映射
+func ConvertSliceToMap[S ~[]V, V comparable](s S) map[V]struct{} {
+ if len(s) == 0 {
+ return nil
+ }
+ var r = make(map[V]struct{}, len(s))
+ for _, v := range s {
+ r[v] = struct{}{}
+ }
+ return r
+}
+
+// ConvertSliceToBoolMap 将切片转换为值为键的映射
+func ConvertSliceToBoolMap[S ~[]V, V comparable](s S) map[V]bool {
+ if len(s) == 0 {
+ return make(map[V]bool)
+ }
+ var r = make(map[V]bool, len(s))
+ for _, v := range s {
+ r[v] = true
+ }
+ return r
+}
+
+// ConvertMapKeysToSlice 将映射的键转换为切片
+func ConvertMapKeysToSlice[M ~map[K]V, K comparable, V any](m M) []K {
+ if len(m) == 0 {
+ return nil
+ }
+ var r = make([]K, 0, len(m))
+ for k := range m {
+ r = append(r, k)
+ }
+ return r
+}
+
+// ConvertMapValuesToSlice 将映射的值转换为切片
+func ConvertMapValuesToSlice[M ~map[K]V, K comparable, V any](m M) []V {
+ if len(m) == 0 {
+ return nil
+ }
+ var r = make([]V, 0, len(m))
+ for _, v := range m {
+ r = append(r, v)
+ }
+ return r
+}
+
+// InvertMap 将映射的键和值互换
+func InvertMap[M ~map[K]V, N map[V]K, K, V comparable](m M) N {
+ if m == nil {
+ return nil
+ }
+ var r = make(N, len(m))
+ for k, v := range m {
+ r[v] = k
+ }
+ return r
+}
+
+// ConvertMapValuesToBool 将映射的值转换为布尔值
+func ConvertMapValuesToBool[M ~map[K]V, N map[K]bool, K comparable, V any](m M) N {
+ if m == nil {
+ return nil
+ }
+ var r = make(N, len(m))
+ for k := range m {
+ r[k] = true
+ }
+ return r
+}
+
+// ReverseSlice 将切片反转
+func ReverseSlice[S ~[]V, V any](s *S) {
+ if s == nil {
+ return
+ }
+
+ var length = len(*s)
+ for i := 0; i < length/2; i++ {
+ (*s)[i], (*s)[length-i-1] = (*s)[length-i-1], (*s)[i]
+ }
+}
diff --git a/toolkit/collection/convert_example_test.go b/toolkit/collection/convert_example_test.go
new file mode 100644
index 0000000..468e6ea
--- /dev/null
+++ b/toolkit/collection/convert_example_test.go
@@ -0,0 +1,130 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+ "reflect"
+ "sort"
+)
+
+func ExampleConvertSliceToBatches() {
+ result := collection.ConvertSliceToBatches([]int{1, 2, 3}, 2)
+ for _, v := range result {
+ fmt.Println(v)
+ }
+ // Output:
+ // [1 2]
+ // [3]
+}
+
+func ExampleConvertMapKeysToBatches() {
+ result := collection.ConvertMapKeysToBatches(map[int]int{1: 1, 2: 2, 3: 3}, 2)
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
+
+func ExampleConvertMapValuesToBatches() {
+ result := collection.ConvertMapValuesToBatches(map[int]int{1: 1, 2: 2, 3: 3}, 2)
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
+
+func ExampleConvertSliceToAny() {
+ result := collection.ConvertSliceToAny([]int{1, 2, 3})
+ fmt.Println(reflect.TypeOf(result).String(), len(result))
+ // Output:
+ // []interface {} 3
+}
+
+func ExampleConvertSliceToIndexMap() {
+ slice := []int{1, 2, 3}
+ result := collection.ConvertSliceToIndexMap(slice)
+ for i, v := range slice {
+ fmt.Println(result[i], v)
+ }
+ // Output:
+ // 1 1
+ // 2 2
+ // 3 3
+}
+
+func ExampleConvertSliceToIndexOnlyMap() {
+ slice := []int{1, 2, 3}
+ result := collection.ConvertSliceToIndexOnlyMap(slice)
+ expected := map[int]bool{0: true, 1: true, 2: true}
+ for k := range result {
+ fmt.Println(expected[k])
+ }
+ // Output:
+ // true
+ // true
+ // true
+}
+
+func ExampleConvertSliceToMap() {
+ slice := []int{1, 2, 3}
+ result := collection.ConvertSliceToMap(slice)
+ fmt.Println(collection.AllKeyInMap(result, slice...))
+ // Output:
+ // true
+}
+
+func ExampleConvertSliceToBoolMap() {
+ slice := []int{1, 2, 3}
+ result := collection.ConvertSliceToBoolMap(slice)
+ for _, v := range slice {
+ fmt.Println(v, result[v])
+ }
+ // Output:
+ // 1 true
+ // 2 true
+ // 3 true
+}
+
+func ExampleConvertMapKeysToSlice() {
+ result := collection.ConvertMapKeysToSlice(map[int]int{1: 1, 2: 2, 3: 3})
+ sort.Ints(result)
+ for i, v := range result {
+ fmt.Println(i, v)
+ }
+ // Output:
+ // 0 1
+ // 1 2
+ // 2 3
+}
+
+func ExampleConvertMapValuesToSlice() {
+ result := collection.ConvertMapValuesToSlice(map[int]int{1: 1, 2: 2, 3: 3})
+ expected := map[int]bool{1: true, 2: true, 3: true}
+ for _, v := range result {
+ fmt.Println(expected[v])
+ }
+ // Output:
+ // true
+ // true
+ // true
+}
+
+func ExampleInvertMap() {
+ result := collection.InvertMap(map[int]string{1: "a", 2: "b", 3: "c"})
+ fmt.Println(collection.AllKeyInMap(result, "a", "b", "c"))
+ // Output:
+ // true
+}
+
+func ExampleConvertMapValuesToBool() {
+ result := collection.ConvertMapValuesToBool(map[int]int{1: 1})
+ fmt.Println(result)
+ // Output:
+ // map[1:true]
+}
+
+func ExampleReverseSlice() {
+ var s = []int{1, 2, 3}
+ collection.ReverseSlice(&s)
+ fmt.Println(s)
+ // Output:
+ // [3 2 1]
+}
diff --git a/toolkit/collection/convert_test.go b/toolkit/collection/convert_test.go
new file mode 100644
index 0000000..a117cc9
--- /dev/null
+++ b/toolkit/collection/convert_test.go
@@ -0,0 +1,377 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "reflect"
+ "testing"
+)
+
+func TestConvertSliceToBatches(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ batch int
+ expected [][]int
+ }{
+ {name: "TestConvertSliceToBatches_NonEmpty", input: []int{1, 2, 3}, batch: 2, expected: [][]int{{1, 2}, {3}}},
+ {name: "TestConvertSliceToBatches_Empty", input: []int{}, batch: 2, expected: nil},
+ {name: "TestConvertSliceToBatches_Nil", input: nil, batch: 2, expected: nil},
+ {name: "TestConvertSliceToBatches_NonPositive", input: []int{1, 2, 3}, batch: 0, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertSliceToBatches(c.input, c.batch)
+ 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 len(av) != len(ev) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for j := 0; j < len(av); j++ {
+ aj, ej := av[j], ev[j]
+ if reflect.TypeOf(aj).Kind() != reflect.TypeOf(ej).Kind() {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ if aj != ej {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestConvertMapKeysToBatches(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ batch int
+ expected [][]int
+ }{
+ {name: "TestConvertMapKeysToBatches_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 2, expected: [][]int{{1, 2}, {3}}},
+ {name: "TestConvertMapKeysToBatches_Empty", input: map[int]int{}, batch: 2, expected: nil},
+ {name: "TestConvertMapKeysToBatches_Nil", input: nil, batch: 2, expected: nil},
+ {name: "TestConvertMapKeysToBatches_NonPositive", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 0, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertMapKeysToBatches(c.input, c.batch)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ })
+ }
+}
+
+func TestConvertMapValuesToBatches(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ batch int
+ expected [][]int
+ }{
+ {name: "TestConvertMapValuesToBatches_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 2, expected: [][]int{{1, 2}, {3}}},
+ {name: "TestConvertMapValuesToBatches_Empty", input: map[int]int{}, batch: 2, expected: nil},
+ {name: "TestConvertMapValuesToBatches_Nil", input: nil, batch: 2, expected: nil},
+ {name: "TestConvertMapValuesToBatches_NonPositive", input: map[int]int{1: 1, 2: 2, 3: 3}, batch: 0, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertMapValuesToBatches(c.input, c.batch)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ })
+ }
+}
+
+func TestConvertSliceToAny(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []interface{}
+ }{
+ {name: "TestConvertSliceToAny_NonEmpty", input: []int{1, 2, 3}, expected: []any{1, 2, 3}},
+ {name: "TestConvertSliceToAny_Empty", input: []int{}, expected: []any{}},
+ {name: "TestConvertSliceToAny_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertSliceToAny(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for i := 0; i < len(actual); i++ {
+ av, ev := actual[i], c.expected[i]
+ if reflect.TypeOf(av).Kind() != reflect.TypeOf(ev).Kind() {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ if av != ev {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestConvertSliceToIndexMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]int
+ }{
+ {name: "TestConvertSliceToIndexMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]int{0: 1, 1: 2, 2: 3}},
+ {name: "TestConvertSliceToIndexMap_Empty", input: []int{}, expected: map[int]int{}},
+ {name: "TestConvertSliceToIndexMap_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertSliceToIndexMap(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for k, v := range actual {
+ if c.expected[k] != v {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestConvertSliceToIndexOnlyMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]struct{}
+ }{
+ {name: "TestConvertSliceToIndexOnlyMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{0: {}, 1: {}, 2: {}}},
+ {name: "TestConvertSliceToIndexOnlyMap_Empty", input: []int{}, expected: map[int]struct{}{}},
+ {name: "TestConvertSliceToIndexOnlyMap_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertSliceToIndexOnlyMap(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for k, v := range actual {
+ if _, ok := c.expected[k]; !ok {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ if v != struct{}{} {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestConvertSliceToMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]struct{}
+ }{
+ {name: "TestConvertSliceToMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{1: {}, 2: {}, 3: {}}},
+ {name: "TestConvertSliceToMap_Empty", input: []int{}, expected: map[int]struct{}{}},
+ {name: "TestConvertSliceToMap_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertSliceToMap(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for k, v := range actual {
+ if _, ok := c.expected[k]; !ok {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ if v != struct{}{} {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestConvertSliceToBoolMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]bool
+ }{
+ {name: "TestConvertSliceToBoolMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]bool{1: true, 2: true, 3: true}},
+ {name: "TestConvertSliceToBoolMap_Empty", input: []int{}, expected: map[int]bool{}},
+ {name: "TestConvertSliceToBoolMap_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertSliceToBoolMap(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for k, v := range actual {
+ if c.expected[k] != v {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestConvertMapKeysToSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected []int
+ }{
+ {name: "TestConvertMapKeysToSlice_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, expected: []int{1, 2, 3}},
+ {name: "TestConvertMapKeysToSlice_Empty", input: map[int]int{}, expected: []int{}},
+ {name: "TestConvertMapKeysToSlice_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertMapKeysToSlice(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ var matchCount = 0
+ for _, av := range actual {
+ for _, ev := range c.expected {
+ if av == ev {
+ matchCount++
+ }
+ }
+ }
+ if matchCount != len(actual) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ })
+ }
+}
+
+func TestConvertMapValuesToSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected []int
+ }{
+ {name: "TestConvertMapValuesToSlice_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, expected: []int{1, 2, 3}},
+ {name: "TestConvertMapValuesToSlice_Empty", input: map[int]int{}, expected: []int{}},
+ {name: "TestConvertMapValuesToSlice_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertMapValuesToSlice(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ var matchCount = 0
+ for _, av := range actual {
+ for _, ev := range c.expected {
+ if av == ev {
+ matchCount++
+ }
+ }
+ }
+ if matchCount != len(actual) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ })
+ }
+}
+
+func TestInvertMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]string
+ expected map[string]int
+ }{
+ {name: "TestInvertMap_NonEmpty", input: map[int]string{1: "1", 2: "2", 3: "3"}, expected: map[string]int{"1": 1, "2": 2, "3": 3}},
+ {name: "TestInvertMap_Empty", input: map[int]string{}, expected: map[string]int{}},
+ {name: "TestInvertMap_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.InvertMap[map[int]string, map[string]int](c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for k, v := range actual {
+ if c.expected[k] != v {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestConvertMapValuesToBool(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {name: "TestConvertMapValuesToBool_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, expected: map[int]bool{1: true, 2: true, 3: true}},
+ {name: "TestConvertMapValuesToBool_Empty", input: map[int]int{}, expected: map[int]bool{}},
+ {name: "TestConvertMapValuesToBool_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ConvertMapValuesToBool[map[int]int, map[int]bool](c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for k, v := range actual {
+ if c.expected[k] != v {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestReverseSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {name: "TestReverseSlice_NonEmpty", input: []int{1, 2, 3}, expected: []int{3, 2, 1}},
+ {name: "TestReverseSlice_Empty", input: []int{}, expected: []int{}},
+ {name: "TestReverseSlice_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.ReverseSlice(&c.input)
+ if len(c.input) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ for i := 0; i < len(c.input); i++ {
+ av, ev := c.input[i], c.expected[i]
+ if reflect.TypeOf(av).Kind() != reflect.TypeOf(ev).Kind() {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ if av != ev {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/doc.go b/toolkit/collection/doc.go
new file mode 100644
index 0000000..195bdbb
--- /dev/null
+++ b/toolkit/collection/doc.go
@@ -0,0 +1,2 @@
+// Package collection 定义了各种对于集合操作有用的各种函数
+package collection
diff --git a/toolkit/collection/drop.go b/toolkit/collection/drop.go
new file mode 100644
index 0000000..d6c1e49
--- /dev/null
+++ b/toolkit/collection/drop.go
@@ -0,0 +1,82 @@
+package collection
+
+// ClearSlice 清空切片
+func ClearSlice[S ~[]V, V any](slice *S) {
+ if slice == nil {
+ return
+ }
+ *slice = (*slice)[0:0]
+}
+
+// ClearMap 清空 map
+func ClearMap[M ~map[K]V, K comparable, V any](m M) {
+ for k := range m {
+ delete(m, k)
+ }
+}
+
+// DropSliceByIndices 删除切片中特定索引的元素
+func DropSliceByIndices[S ~[]V, V any](slice *S, indices ...int) {
+ if slice == nil {
+ return
+ }
+ if len(indices) == 0 {
+ return
+ }
+
+ excludeMap := make(map[int]bool)
+ for _, ex := range indices {
+ excludeMap[ex] = true
+ }
+
+ validElements := (*slice)[:0]
+ for i, v := range *slice {
+ if !excludeMap[i] {
+ validElements = append(validElements, v)
+ }
+ }
+
+ *slice = validElements
+}
+
+// DropSliceByCondition 删除切片中符合条件的元素
+// - condition 的返回值为 true 时,将会删除该元素
+func DropSliceByCondition[S ~[]V, V any](slice *S, condition func(v V) bool) {
+ if slice == nil {
+ return
+ }
+ if condition == nil {
+ return
+ }
+
+ validElements := (*slice)[:0]
+ for _, v := range *slice {
+ if !condition(v) {
+ validElements = append(validElements, v)
+ }
+ }
+
+ *slice = validElements
+}
+
+// DropSliceOverlappingElements 删除切片中与另一个切片重叠的元素
+func DropSliceOverlappingElements[S ~[]V, V any](slice *S, anotherSlice []V, comparisonHandler ComparisonHandler[V]) {
+ if slice == nil {
+ return
+ }
+ if anotherSlice == nil {
+ return
+ }
+ if comparisonHandler == nil {
+ return
+ }
+
+ validElements := (*slice)[:0]
+ for _, v := range *slice {
+ if !InSlice(anotherSlice, v, comparisonHandler) {
+ validElements = append(validElements, v)
+ }
+ }
+
+ *slice = validElements
+}
diff --git a/toolkit/collection/drop_example_test.go b/toolkit/collection/drop_example_test.go
new file mode 100644
index 0000000..490e7c5
--- /dev/null
+++ b/toolkit/collection/drop_example_test.go
@@ -0,0 +1,47 @@
+package collection
+
+import "fmt"
+
+func ExampleClearSlice() {
+ slice := []int{1, 2, 3, 4, 5}
+ ClearSlice(&slice)
+ fmt.Println(slice)
+ // Output:
+ // []
+}
+
+func ExampleClearMap() {
+ m := map[int]int{1: 1, 2: 2, 3: 3}
+ ClearMap(m)
+ fmt.Println(m)
+ // Output:
+ // map[]
+}
+
+func ExampleDropSliceByIndices() {
+ slice := []int{1, 2, 3, 4, 5}
+ DropSliceByIndices(&slice, 1, 3)
+ fmt.Println(slice)
+ // Output:
+ // [1 3 5]
+}
+
+func ExampleDropSliceByCondition() {
+ slice := []int{1, 2, 3, 4, 5}
+ DropSliceByCondition(&slice, func(v int) bool {
+ return v%2 == 0
+ })
+ fmt.Println(slice)
+ // Output:
+ // [1 3 5]
+}
+
+func ExampleDropSliceOverlappingElements() {
+ slice := []int{1, 2, 3, 4, 5}
+ DropSliceOverlappingElements(&slice, []int{1, 3, 5}, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(slice)
+ // Output:
+ // [2 4]
+}
diff --git a/toolkit/collection/drop_test.go b/toolkit/collection/drop_test.go
new file mode 100644
index 0000000..b4540e9
--- /dev/null
+++ b/toolkit/collection/drop_test.go
@@ -0,0 +1,145 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestClearSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestClearSlice_NonEmptySlice", []int{1, 2, 3}, []int{}},
+ {"TestClearSlice_EmptySlice", []int{}, []int{}},
+ {"TestClearSlice_NilSlice", nil, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.ClearSlice(&c.input)
+ if len(c.input) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the length of input is not equal")
+ }
+ for i := 0; i < len(c.input); i++ {
+ if c.input[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestClearMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]int
+ }{
+ {"TestClearMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{}},
+ {"TestClearMap_EmptyMap", map[int]int{}, map[int]int{}},
+ {"TestClearMap_NilMap", nil, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.ClearMap(c.input)
+ if len(c.input) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the length of map is not equal")
+ }
+ for k, v := range c.input {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestDropSliceByIndices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ indices []int
+ expected []int
+ }{
+ {"TestDropSliceByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{1, 3}, []int{1, 3, 5}},
+ {"TestDropSliceByIndices_EmptySlice", []int{}, []int{1, 3}, []int{}},
+ {"TestDropSliceByIndices_NilSlice", nil, []int{1, 3}, nil},
+ {"TestDropSliceByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{}, []int{1, 2, 3, 4, 5}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.DropSliceByIndices(&c.input, c.indices...)
+ if len(c.input) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
+ }
+ for i := 0; i < len(c.input); i++ {
+ if c.input[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestDropSliceByCondition(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ condition func(v int) bool
+ expected []int
+ }{
+ {"TestDropSliceByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 0 }, []int{1, 3, 5}},
+ {"TestDropSliceByCondition_EmptySlice", []int{}, func(v int) bool { return v%2 == 0 }, []int{}},
+ {"TestDropSliceByCondition_NilSlice", nil, func(v int) bool { return v%2 == 0 }, nil},
+ {"TestDropSliceByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 1 }, []int{2, 4}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.DropSliceByCondition(&c.input, c.condition)
+ if len(c.input) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
+ }
+ for i := 0; i < len(c.input); i++ {
+ if c.input[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestDropSliceOverlappingElements(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ anotherSlice []int
+ comparisonHandler collection.ComparisonHandler[int]
+ expected []int
+ expectedAnother []int
+ expectedComparison []int
+ }{
+ {"TestDropSliceOverlappingElements_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, []int{1, 2}, []int{6, 7}, []int{3, 4, 5}},
+ {"TestDropSliceOverlappingElements_EmptySlice", []int{}, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, []int{}, []int{3, 4, 5, 6, 7}, []int{}},
+ {"TestDropSliceOverlappingElements_NilSlice", nil, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, nil, []int{3, 4, 5, 6, 7}, nil},
+ {"TestDropSliceOverlappingElements_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{}, func(v1, v2 int) bool { return v1 == v2 }, []int{1, 2, 3, 4, 5}, []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.DropSliceOverlappingElements(&c.input, c.anotherSlice, c.comparisonHandler)
+ if len(c.input) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
+ }
+ for i := 0; i < len(c.input); i++ {
+ if c.input[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/duplicate.go b/toolkit/collection/duplicate.go
new file mode 100644
index 0000000..5f6907c
--- /dev/null
+++ b/toolkit/collection/duplicate.go
@@ -0,0 +1,83 @@
+package collection
+
+// DeduplicateSliceInPlace 去除切片中的重复元素
+func DeduplicateSliceInPlace[S ~[]V, V comparable](s *S) {
+ if s == nil || len(*s) < 2 {
+ return
+ }
+
+ var m = make(map[V]struct{}, len(*s))
+ var writeIndex int
+ for readIndex, v := range *s {
+ if _, ok := m[v]; !ok {
+ (*s)[writeIndex] = (*s)[readIndex]
+ writeIndex++
+ m[v] = struct{}{}
+ }
+ }
+ *s = (*s)[:writeIndex]
+}
+
+// DeduplicateSlice 去除切片中的重复元素,返回新切片
+func DeduplicateSlice[S ~[]V, V comparable](s S) S {
+ if s == nil || len(s) < 2 {
+ return any(s).(S)
+ }
+
+ var r = make([]V, 0, len(s))
+ var m = make(map[V]struct{}, len(s))
+ for _, v := range s {
+ if _, ok := m[v]; !ok {
+ r = append(r, v)
+ m[v] = struct{}{}
+ }
+ }
+ return r
+}
+
+// DeduplicateSliceInPlaceWithCompare 去除切片中的重复元素,使用自定义的比较函数
+func DeduplicateSliceInPlaceWithCompare[S ~[]V, V any](s *S, compare func(a, b V) bool) {
+ if s == nil || len(*s) < 2 {
+ return
+ }
+ seen := make(map[int]struct{})
+ resultIndex := 0
+ for i := range *s {
+ unique := true
+ for j := range seen {
+ if compare((*s)[i], (*s)[j]) {
+ unique = false // Found a duplicate
+ break
+ }
+ }
+ if unique {
+ seen[i] = struct{}{}
+ (*s)[resultIndex] = (*s)[i]
+ resultIndex++
+ }
+ }
+ *s = (*s)[:resultIndex]
+}
+
+// DeduplicateSliceWithCompare 去除切片中的重复元素,使用自定义的比较函数,返回新的切片
+func DeduplicateSliceWithCompare[S ~[]V, V any](s S, compare func(a, b V) bool) S {
+ if s == nil || compare == nil || len(s) < 2 {
+ return s
+ }
+ seen := make(map[int]struct{})
+ var result = make([]V, 0, len(s))
+ for i := range s {
+ unique := true
+ for j := range result {
+ if compare(s[i], result[j]) {
+ unique = false
+ break
+ }
+ }
+ if unique {
+ result = append(result, s[i])
+ seen[i] = struct{}{}
+ }
+ }
+ return result
+}
diff --git a/toolkit/collection/duplicate_example_test.go b/toolkit/collection/duplicate_example_test.go
new file mode 100644
index 0000000..026903c
--- /dev/null
+++ b/toolkit/collection/duplicate_example_test.go
@@ -0,0 +1,37 @@
+package collection
+
+import "fmt"
+
+func ExampleDeduplicateSliceInPlace() {
+ slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
+ DeduplicateSliceInPlace(&slice)
+ fmt.Println(slice)
+ // Output:
+ // [1 2 3 4 5]
+}
+
+func ExampleDeduplicateSlice() {
+ slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
+ fmt.Println(DeduplicateSlice(slice))
+ // Output:
+ // [1 2 3 4 5]
+}
+
+func ExampleDeduplicateSliceInPlaceWithCompare() {
+ slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
+ DeduplicateSliceInPlaceWithCompare(&slice, func(a, b int) bool {
+ return a == b
+ })
+ fmt.Println(slice)
+ // Output:
+ // [1 2 3 4 5]
+}
+
+func ExampleDeduplicateSliceWithCompare() {
+ slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
+ fmt.Println(DeduplicateSliceWithCompare(slice, func(a, b int) bool {
+ return a == b
+ }))
+ // Output:
+ // [1 2 3 4 5]
+}
diff --git a/toolkit/collection/duplicate_test.go b/toolkit/collection/duplicate_test.go
new file mode 100644
index 0000000..2632441
--- /dev/null
+++ b/toolkit/collection/duplicate_test.go
@@ -0,0 +1,118 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestDeduplicateSliceInPlace(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {name: "TestDeduplicateSliceInPlace_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
+ {name: "TestDeduplicateSliceInPlace_Empty", input: []int{}, expected: []int{}},
+ {name: "TestDeduplicateSliceInPlace_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.DeduplicateSliceInPlace(&c.input)
+ if len(c.input) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ for i := 0; i < len(c.input); i++ {
+ av, ev := c.input[i], c.expected[i]
+ if av != ev {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ }
+ })
+ }
+}
+
+func TestDeduplicateSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {name: "TestDeduplicateSlice_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
+ {name: "TestDeduplicateSlice_Empty", input: []int{}, expected: []int{}},
+ {name: "TestDeduplicateSlice_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.DeduplicateSlice(c.input)
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for i := 0; i < len(actual); i++ {
+ av, ev := actual[i], c.expected[i]
+ if av != ev {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
+
+func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {name: "TestDeduplicateSliceInPlaceWithCompare_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
+ {name: "TestDeduplicateSliceInPlaceWithCompare_Empty", input: []int{}, expected: []int{}},
+ {name: "TestDeduplicateSliceInPlaceWithCompare_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.DeduplicateSliceInPlaceWithCompare(&c.input, func(a, b int) bool {
+ return a == b
+ })
+ if len(c.input) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ for i := 0; i < len(c.input); i++ {
+ av, ev := c.input[i], c.expected[i]
+ if av != ev {
+ t.Errorf("expected: %v, actual: %v", c.expected, c.input)
+ }
+ }
+ })
+ }
+}
+
+func TestDeduplicateSliceWithCompare(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {name: "TestDeduplicateSliceWithCompare_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
+ {name: "TestDeduplicateSliceWithCompare_Empty", input: []int{}, expected: []int{}},
+ {name: "TestDeduplicateSliceWithCompare_Nil", input: nil, expected: nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.DeduplicateSliceWithCompare(c.input, func(a, b int) bool {
+ return a == b
+ })
+ if len(actual) != len(c.expected) {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ for i := 0; i < len(actual); i++ {
+ av, ev := actual[i], c.expected[i]
+ if av != ev {
+ t.Errorf("expected: %v, actual: %v", c.expected, actual)
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/filter.go b/toolkit/collection/filter.go
new file mode 100644
index 0000000..57ceab7
--- /dev/null
+++ b/toolkit/collection/filter.go
@@ -0,0 +1,140 @@
+package collection
+
+// FilterOutByIndices 过滤切片中特定索引的元素,返回过滤后的切片
+func FilterOutByIndices[S []V, V any](slice S, indices ...int) S {
+ if slice == nil || len(slice) == 0 || len(indices) == 0 {
+ return slice
+ }
+
+ excludeMap := make(map[int]bool)
+ for _, ex := range indices {
+ if ex >= 0 && ex < len(slice) {
+ excludeMap[ex] = true
+ }
+ }
+
+ if len(excludeMap) == 0 {
+ return slice
+ }
+
+ validElements := make([]V, 0, len(slice)-len(excludeMap))
+ for i, v := range slice {
+ if !excludeMap[i] {
+ validElements = append(validElements, v)
+ }
+ }
+
+ return validElements
+}
+
+// FilterOutByCondition 过滤切片中符合条件的元素,返回过滤后的切片
+// - condition 的返回值为 true 时,将会过滤掉该元素
+func FilterOutByCondition[S ~[]V, V any](slice S, condition func(v V) bool) S {
+ if slice == nil {
+ return nil
+ }
+ if condition == nil {
+ return slice
+ }
+
+ validElements := make([]V, 0, len(slice))
+ for _, v := range slice {
+ if !condition(v) {
+ validElements = append(validElements, v)
+ }
+ }
+
+ return validElements
+}
+
+// FilterOutByKey 过滤 map 中特定的 key,返回过滤后的 map
+func FilterOutByKey[M ~map[K]V, K comparable, V any](m M, key K) M {
+ if m == nil {
+ return nil
+ }
+
+ validMap := make(M, len(m)-1)
+ for k, v := range m {
+ if k != key {
+ validMap[k] = v
+ }
+ }
+
+ return validMap
+}
+
+// FilterOutByValue 过滤 map 中特定的 value,返回过滤后的 map
+func FilterOutByValue[M ~map[K]V, K comparable, V any](m M, value V, handler ComparisonHandler[V]) M {
+ if m == nil {
+ return nil
+ }
+
+ validMap := make(M, len(m))
+ for k, v := range m {
+ if !handler(value, v) {
+ validMap[k] = v
+ }
+ }
+
+ return validMap
+}
+
+// FilterOutByKeys 过滤 map 中多个 key,返回过滤后的 map
+func FilterOutByKeys[M ~map[K]V, K comparable, V any](m M, keys ...K) M {
+ if m == nil {
+ return nil
+ }
+ if len(keys) == 0 {
+ return m
+ }
+
+ validMap := make(M, len(m)-len(keys))
+ for k, v := range m {
+ if !InSlice(keys, k, func(source, target K) bool {
+ return source == target
+ }) {
+ validMap[k] = v
+ }
+ }
+
+ return validMap
+}
+
+// FilterOutByValues 过滤 map 中多个 values,返回过滤后的 map
+func FilterOutByValues[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) M {
+ if m == nil {
+ return nil
+ }
+ if len(values) == 0 {
+ return m
+ }
+
+ validMap := make(M, len(m))
+ for k, v := range m {
+ if !InSlice(values, v, handler) {
+ validMap[k] = v
+ }
+ }
+
+ return validMap
+}
+
+// FilterOutByMap 过滤 map 中符合条件的元素,返回过滤后的 map
+// - condition 的返回值为 true 时,将会过滤掉该元素
+func FilterOutByMap[M ~map[K]V, K comparable, V any](m M, condition func(k K, v V) bool) M {
+ if m == nil {
+ return nil
+ }
+ if condition == nil {
+ return m
+ }
+
+ validMap := make(M, len(m))
+ for k, v := range m {
+ if !condition(k, v) {
+ validMap[k] = v
+ }
+ }
+
+ return validMap
+}
diff --git a/toolkit/collection/filter_example_test.go b/toolkit/collection/filter_example_test.go
new file mode 100644
index 0000000..cd80fac
--- /dev/null
+++ b/toolkit/collection/filter_example_test.go
@@ -0,0 +1,74 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleFilterOutByIndices() {
+ var slice = []int{1, 2, 3, 4, 5}
+ var result = collection.FilterOutByIndices(slice, 1, 3)
+ fmt.Println(result)
+ // Output:
+ // [1 3 5]
+}
+
+func ExampleFilterOutByCondition() {
+ var slice = []int{1, 2, 3, 4, 5}
+ var result = collection.FilterOutByCondition(slice, func(v int) bool {
+ return v%2 == 0
+ })
+ fmt.Println(result)
+ // Output:
+ // [1 3 5]
+}
+
+func ExampleFilterOutByKey() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result = collection.FilterOutByKey(m, "b")
+ fmt.Println(result)
+ // Output:
+ // map[a:1 c:3]
+}
+
+func ExampleFilterOutByValue() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result = collection.FilterOutByValue(m, 2, func(source, target int) bool {
+ return source == target
+ })
+ fmt.Println(len(result))
+ // Output:
+ // 2
+}
+
+func ExampleFilterOutByKeys() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result = collection.FilterOutByKeys(m, "a", "c")
+ fmt.Println(result)
+ // Output:
+ // map[b:2]
+}
+
+func ExampleFilterOutByValues() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result = collection.FilterOutByValues(m, []int{1}, func(source, target int) bool {
+ return source == target
+ })
+ for i, s := range []string{"a", "b", "c"} {
+ fmt.Println(i, result[s])
+ }
+ // Output:
+ // 0 0
+ // 1 2
+ // 2 3
+}
+
+func ExampleFilterOutByMap() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result = collection.FilterOutByMap(m, func(k string, v int) bool {
+ return k == "a" || v == 3
+ })
+ fmt.Println(result)
+ // Output:
+ // map[b:2]
+}
diff --git a/toolkit/collection/filter_test.go b/toolkit/collection/filter_test.go
new file mode 100644
index 0000000..edc1d55
--- /dev/null
+++ b/toolkit/collection/filter_test.go
@@ -0,0 +1,207 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestFilterOutByIndices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ indices []int
+ expected []int
+ }{
+ {"TestFilterOutByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{1, 3}, []int{1, 3, 5}},
+ {"TestFilterOutByIndices_EmptySlice", []int{}, []int{1, 3}, []int{}},
+ {"TestFilterOutByIndices_NilSlice", nil, []int{1, 3}, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByIndices(c.input, c.indices...)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of input is not equal")
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestFilterOutByCondition(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ condition func(int) bool
+ expected []int
+ }{
+ {"TestFilterOutByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool {
+ return v%2 == 0
+ }, []int{1, 3, 5}},
+ {"TestFilterOutByCondition_EmptySlice", []int{}, func(v int) bool {
+ return v%2 == 0
+ }, []int{}},
+ {"TestFilterOutByCondition_NilSlice", nil, func(v int) bool {
+ return v%2 == 0
+ }, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByCondition(c.input, c.condition)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of input is not equal")
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestFilterOutByKey(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ key int
+ expected map[int]int
+ }{
+ {"TestFilterOutByKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, map[int]int{2: 2, 3: 3}},
+ {"TestFilterOutByKey_EmptyMap", map[int]int{}, 1, map[int]int{}},
+ {"TestFilterOutByKey_NilMap", nil, 1, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByKey(c.input, c.key)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
+ }
+ for k, v := range actual {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestFilterOutByValue(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ value int
+ expected map[int]int
+ }{
+ {"TestFilterOutByValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, map[int]int{2: 2, 3: 3}},
+ {"TestFilterOutByValue_EmptyMap", map[int]int{}, 1, map[int]int{}},
+ {"TestFilterOutByValue_NilMap", nil, 1, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByValue(c.input, c.value, func(source, target int) bool {
+ return source == target
+ })
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
+ }
+ for k, v := range actual {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestFilterOutByKeys(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ keys []int
+ expected map[int]int
+ }{
+ {"TestFilterOutByKeys_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, []int{1, 3}, map[int]int{2: 2}},
+ {"TestFilterOutByKeys_EmptyMap", map[int]int{}, []int{1, 3}, map[int]int{}},
+ {"TestFilterOutByKeys_NilMap", nil, []int{1, 3}, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByKeys(c.input, c.keys...)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
+ }
+ for k, v := range actual {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestFilterOutByValues(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ values []int
+ expected map[int]int
+ }{
+ {"TestFilterOutByValues_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, []int{1, 3}, map[int]int{2: 2}},
+ {"TestFilterOutByValues_EmptyMap", map[int]int{}, []int{1, 3}, map[int]int{}},
+ {"TestFilterOutByValues_NilMap", nil, []int{1, 3}, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByValues(c.input, c.values, func(source, target int) bool {
+ return source == target
+ })
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
+ }
+ for k, v := range actual {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestFilterOutByMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ filter map[int]int
+ expected map[int]int
+ }{
+ {"TestFilterOutByMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 1, 3: 3}, map[int]int{2: 2}},
+ {"TestFilterOutByMap_EmptyMap", map[int]int{}, map[int]int{1: 1, 3: 3}, map[int]int{}},
+ {"TestFilterOutByMap_NilMap", nil, map[int]int{1: 1, 3: 3}, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FilterOutByMap(c.input, func(k int, v int) bool {
+ return c.filter[k] == v
+ })
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
+ }
+ for k, v := range actual {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/find.go b/toolkit/collection/find.go
new file mode 100644
index 0000000..c773615
--- /dev/null
+++ b/toolkit/collection/find.go
@@ -0,0 +1,348 @@
+package collection
+
+import (
+ "github.com/kercylan98/minotaur/utils/generic"
+)
+
+// FindLoopedNextInSlice 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始
+// - 当 i 为负数时将返回第一个元素
+func FindLoopedNextInSlice[S ~[]V, V any](slice S, i int) (next int, value V) {
+ if i < 0 {
+ return 0, slice[0]
+ }
+ next = i + 1
+ if next == len(slice) {
+ next = 0
+ }
+ return next, slice[next]
+}
+
+// FindLoopedPrevInSlice 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始
+// - 当 i 为负数时将返回最后一个元素
+func FindLoopedPrevInSlice[S ~[]V, V any](slice S, i int) (prev int, value V) {
+ if i < 0 {
+ return len(slice) - 1, slice[len(slice)-1]
+ }
+ prev = i - 1
+ if prev == -1 {
+ prev = len(slice) - 1
+ }
+ return prev, slice[prev]
+}
+
+// FindCombinationsInSliceByRange 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内
+func FindCombinationsInSliceByRange[S ~[]V, V any](s S, minSize, maxSize int) []S {
+ n := len(s)
+ if n == 0 || minSize <= 0 || maxSize <= 0 || minSize > maxSize {
+ return nil
+ }
+
+ var result []S
+ var currentCombination S
+
+ var backtrack func(startIndex int, currentSize int)
+ backtrack = func(startIndex int, currentSize int) {
+ if currentSize >= minSize && currentSize <= maxSize {
+ combination := make(S, len(currentCombination))
+ copy(combination, currentCombination)
+ result = append(result, combination)
+ }
+
+ for i := startIndex; i < n; i++ {
+ currentCombination = append(currentCombination, s[i])
+ backtrack(i+1, currentSize+1)
+ currentCombination = currentCombination[:len(currentCombination)-1]
+ }
+ }
+
+ backtrack(0, 0)
+ return result
+}
+
+// FindFirstOrDefaultInSlice 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值
+func FindFirstOrDefaultInSlice[S ~[]V, V any](slice S, defaultValue V) V {
+ if len(slice) == 0 {
+ return defaultValue
+ }
+ return slice[0]
+}
+
+// FindOrDefaultInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值
+func FindOrDefaultInSlice[S ~[]V, V any](slice S, defaultValue V, handler func(v V) bool) (t V) {
+ if len(slice) == 0 {
+ return defaultValue
+ }
+ for _, v := range slice {
+ if handler(v) {
+ return v
+ }
+ }
+ return defaultValue
+}
+
+// FindOrDefaultInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值
+func FindOrDefaultInComparableSlice[S ~[]V, V comparable](slice S, v V, defaultValue V) (t V) {
+ if len(slice) == 0 {
+ return defaultValue
+ }
+ for _, value := range slice {
+ if value == v {
+ return value
+ }
+ }
+ return defaultValue
+}
+
+// FindInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1
+func FindInSlice[S ~[]V, V any](slice S, handler func(v V) bool) (i int, t V) {
+ if len(slice) == 0 {
+ return -1, t
+ }
+ for i, v := range slice {
+ if handler(v) {
+ return i, v
+ }
+ }
+ return -1, t
+}
+
+// FindIndexInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1
+func FindIndexInSlice[S ~[]V, V any](slice S, handler func(v V) bool) int {
+ if len(slice) == 0 {
+ return -1
+ }
+ for i, v := range slice {
+ if handler(v) {
+ return i
+ }
+ }
+ return -1
+}
+
+// FindInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1
+func FindInComparableSlice[S ~[]V, V comparable](slice S, v V) (i int, t V) {
+ if len(slice) == 0 {
+ return -1, t
+ }
+ for i, value := range slice {
+ if value == v {
+ return i, value
+ }
+ }
+ return -1, t
+}
+
+// FindIndexInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1
+func FindIndexInComparableSlice[S ~[]V, V comparable](slice S, v V) int {
+ if len(slice) == 0 {
+ return -1
+ }
+ for i, value := range slice {
+ if value == v {
+ return i
+ }
+ }
+ return -1
+}
+
+// FindMinimumInComparableSlice 获取切片中的最小值
+func FindMinimumInComparableSlice[S ~[]V, V generic.Ordered](slice S) (result V) {
+ if len(slice) == 0 {
+ return
+ }
+ result = slice[0]
+ for i := 1; i < len(slice); i++ {
+ if result > slice[i] {
+ result = slice[i]
+ }
+ }
+ return
+}
+
+// FindMinimumInSlice 获取切片中的最小值
+func FindMinimumInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (result V) {
+ if len(slice) == 0 {
+ return
+ }
+ result = slice[0]
+ for i := 1; i < len(slice); i++ {
+ if handler(result) > handler(slice[i]) {
+ result = slice[i]
+ }
+ }
+ return
+}
+
+// FindMaximumInComparableSlice 获取切片中的最大值
+func FindMaximumInComparableSlice[S ~[]V, V generic.Ordered](slice S) (result V) {
+ if len(slice) == 0 {
+ return
+ }
+ result = slice[0]
+ for i := 1; i < len(slice); i++ {
+ if result < slice[i] {
+ result = slice[i]
+ }
+ }
+ return
+}
+
+// FindMaximumInSlice 获取切片中的最大值
+func FindMaximumInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (result V) {
+ if len(slice) == 0 {
+ return
+ }
+ result = slice[0]
+ for i := 1; i < len(slice); i++ {
+ if handler(result) < handler(slice[i]) {
+ result = slice[i]
+ }
+ }
+ return
+}
+
+// FindMin2MaxInComparableSlice 获取切片中的最小值和最大值
+func FindMin2MaxInComparableSlice[S ~[]V, V generic.Ordered](slice S) (min, max V) {
+ if len(slice) == 0 {
+ return
+ }
+ min = slice[0]
+ max = slice[0]
+ for i := 1; i < len(slice); i++ {
+ if min > slice[i] {
+ min = slice[i]
+ }
+ if max < slice[i] {
+ max = slice[i]
+ }
+ }
+ return
+}
+
+// FindMin2MaxInSlice 获取切片中的最小值和最大值
+func FindMin2MaxInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (min, max V) {
+ if len(slice) == 0 {
+ return
+ }
+ min = slice[0]
+ max = slice[0]
+ for i := 1; i < len(slice); i++ {
+ if handler(min) > handler(slice[i]) {
+ min = slice[i]
+ }
+ if handler(max) < handler(slice[i]) {
+ max = slice[i]
+ }
+ }
+ return
+}
+
+// FindMinFromComparableMap 获取 map 中的最小值
+func FindMinFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (result V) {
+ if m == nil {
+ return
+ }
+ var first bool
+ for _, v := range m {
+ if !first {
+ result = v
+ first = true
+ continue
+ }
+ if result > v {
+ result = v
+ }
+ }
+ return
+}
+
+// FindMinFromMap 获取 map 中的最小值
+func FindMinFromMap[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, handler OrderedValueGetter[V, N]) (result V) {
+ if m == nil {
+ return
+ }
+ var first bool
+ for _, v := range m {
+ if !first {
+ result = v
+ first = true
+ continue
+ }
+ if handler(result) > handler(v) {
+ result = v
+ }
+ }
+ return
+}
+
+// FindMaxFromComparableMap 获取 map 中的最大值
+func FindMaxFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (result V) {
+ if m == nil {
+ return
+ }
+ for _, v := range m {
+ if result < v {
+ result = v
+ }
+ }
+ return
+}
+
+// FindMaxFromMap 获取 map 中的最大值
+func FindMaxFromMap[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, handler OrderedValueGetter[V, N]) (result V) {
+ if m == nil {
+ return
+ }
+ for _, v := range m {
+ if handler(result) < handler(v) {
+ result = v
+ }
+ }
+ return
+}
+
+// FindMin2MaxFromComparableMap 获取 map 中的最小值和最大值
+func FindMin2MaxFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (min, max V) {
+ if m == nil {
+ return
+ }
+ var first bool
+ for _, v := range m {
+ if !first {
+ min = v
+ max = v
+ first = true
+ continue
+ }
+ if min > v {
+ min = v
+ }
+ if max < v {
+ max = v
+ }
+ }
+ return
+}
+
+// FindMin2MaxFromMap 获取 map 中的最小值和最大值
+func FindMin2MaxFromMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (min, max V) {
+ if m == nil {
+ return
+ }
+ var first bool
+ for _, v := range m {
+ if !first {
+ min = v
+ max = v
+ first = true
+ continue
+ }
+ if min > v {
+ min = v
+ }
+ if max < v {
+ max = v
+ }
+ }
+ return
+}
diff --git a/toolkit/collection/find_example_test.go b/toolkit/collection/find_example_test.go
new file mode 100644
index 0000000..3c73c5e
--- /dev/null
+++ b/toolkit/collection/find_example_test.go
@@ -0,0 +1,176 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleFindLoopedNextInSlice() {
+ next, v := collection.FindLoopedNextInSlice([]int{1, 2, 3}, 1)
+ fmt.Println(next, v)
+ // Output:
+ // 2 3
+}
+
+func ExampleFindLoopedPrevInSlice() {
+ prev, v := collection.FindLoopedPrevInSlice([]int{1, 2, 3}, 1)
+ fmt.Println(prev, v)
+ // Output:
+ // 0 1
+}
+
+func ExampleFindCombinationsInSliceByRange() {
+ result := collection.FindCombinationsInSliceByRange([]int{1, 2, 3}, 1, 2)
+ fmt.Println(len(result))
+ // Output:
+ // 6
+}
+
+func ExampleFindFirstOrDefaultInSlice() {
+ result := collection.FindFirstOrDefaultInSlice([]int{1, 2, 3}, 0)
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindOrDefaultInSlice() {
+ result := collection.FindOrDefaultInSlice([]int{1, 2, 3}, 0, func(v int) bool {
+ return v == 2
+ })
+ fmt.Println(result)
+ // Output:
+ // 2
+}
+
+func ExampleFindOrDefaultInComparableSlice() {
+ result := collection.FindOrDefaultInComparableSlice([]int{1, 2, 3}, 2, 0)
+ fmt.Println(result)
+ // Output:
+ // 2
+}
+
+func ExampleFindInSlice() {
+ _, result := collection.FindInSlice([]int{1, 2, 3}, func(v int) bool {
+ return v == 2
+ })
+ fmt.Println(result)
+ // Output:
+ // 2
+}
+
+func ExampleFindIndexInSlice() {
+ result := collection.FindIndexInSlice([]int{1, 2, 3}, func(v int) bool {
+ return v == 2
+ })
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindInComparableSlice() {
+ index, result := collection.FindInComparableSlice([]int{1, 2, 3}, 2)
+ fmt.Println(index, result)
+ // Output:
+ // 1 2
+}
+
+func ExampleFindIndexInComparableSlice() {
+ result := collection.FindIndexInComparableSlice([]int{1, 2, 3}, 2)
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindMinimumInComparableSlice() {
+ result := collection.FindMinimumInComparableSlice([]int{1, 2, 3})
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindMinimumInSlice() {
+ result := collection.FindMinimumInSlice([]int{1, 2, 3}, func(v int) int {
+ return v
+ })
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindMaximumInComparableSlice() {
+ result := collection.FindMaximumInComparableSlice([]int{1, 2, 3})
+ fmt.Println(result)
+ // Output:
+ // 3
+}
+
+func ExampleFindMaximumInSlice() {
+ result := collection.FindMaximumInSlice([]int{1, 2, 3}, func(v int) int {
+ return v
+ })
+ fmt.Println(result)
+ // Output:
+ // 3
+}
+
+func ExampleFindMin2MaxInComparableSlice() {
+ minimum, maximum := collection.FindMin2MaxInComparableSlice([]int{1, 2, 3})
+ fmt.Println(minimum, maximum)
+ // Output:
+ // 1 3
+}
+
+func ExampleFindMin2MaxInSlice() {
+ minimum, maximum := collection.FindMin2MaxInSlice([]int{1, 2, 3}, func(v int) int {
+ return v
+ })
+ fmt.Println(minimum, maximum)
+ // Output:
+ // 1 3
+}
+
+func ExampleFindMinFromComparableMap() {
+ result := collection.FindMinFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindMinFromMap() {
+ result := collection.FindMinFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int {
+ return v
+ })
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleFindMaxFromComparableMap() {
+ result := collection.FindMaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
+ fmt.Println(result)
+ // Output:
+ // 3
+}
+
+func ExampleFindMaxFromMap() {
+ result := collection.FindMaxFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int {
+ return v
+ })
+ fmt.Println(result)
+ // Output:
+ // 3
+}
+
+func ExampleFindMin2MaxFromComparableMap() {
+ minimum, maximum := collection.FindMin2MaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
+ fmt.Println(minimum, maximum)
+ // Output:
+ // 1 3
+}
+
+func ExampleFindMin2MaxFromMap() {
+ minimum, maximum := collection.FindMin2MaxFromMap(map[int]int{1: 1, 2: 2, 3: 3})
+ fmt.Println(minimum, maximum)
+ // Output:
+ // 1 3
+}
diff --git a/toolkit/collection/find_test.go b/toolkit/collection/find_test.go
new file mode 100644
index 0000000..8838ea6
--- /dev/null
+++ b/toolkit/collection/find_test.go
@@ -0,0 +1,467 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestFindLoopedNextInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ i int
+ expected int
+ }{
+ {"TestFindLoopedNextInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 2},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual, _ := collection.FindLoopedNextInSlice(c.input, c.i)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the next index of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindLoopedPrevInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ i int
+ expected int
+ }{
+ {"TestFindLoopedPrevInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual, _ := collection.FindLoopedPrevInSlice(c.input, c.i)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the prev index of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindCombinationsInSliceByRange(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ minSize int
+ maxSize int
+ expected [][]int
+ }{
+ {"TestFindCombinationsInSliceByRange_NonEmptySlice", []int{1, 2, 3}, 1, 2, [][]int{{1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindCombinationsInSliceByRange(c.input, c.minSize, c.maxSize)
+ if len(actual) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindFirstOrDefaultInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindFirstOrDefaultInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
+ {"TestFindFirstOrDefaultInSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindFirstOrDefaultInSlice(c.input, 0)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the first element of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindOrDefaultInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindOrDefaultInSlice_NonEmptySlice", []int{1, 2, 3}, 2},
+ {"TestFindOrDefaultInSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindOrDefaultInSlice(c.input, 0, func(v int) bool {
+ return v == 2
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindOrDefaultInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindOrDefaultInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2},
+ {"TestFindOrDefaultInComparableSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindOrDefaultInComparableSlice(c.input, 2, 0)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindInSlice_NonEmptySlice", []int{1, 2, 3}, 2},
+ {"TestFindInSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ _, actual := collection.FindInSlice(c.input, func(v int) bool {
+ return v == 2
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindIndexInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindIndexInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
+ {"TestFindIndexInSlice_EmptySlice", []int{}, -1},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindIndexInSlice(c.input, func(v int) bool {
+ return v == 2
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2},
+ {"TestFindInComparableSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ _, actual := collection.FindInComparableSlice(c.input, 2)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindIndexInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindIndexInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1},
+ {"TestFindIndexInComparableSlice_EmptySlice", []int{}, -1},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindIndexInComparableSlice(c.input, 2)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMinimumInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindMinimumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1},
+ {"TestFindMinimumInComparableSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMinimumInComparableSlice(c.input)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMinimumInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindMinimumInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
+ {"TestFindMinimumInSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMinimumInSlice(c.input, func(v int) int {
+ return v
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMaximumInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindMaximumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 3},
+ {"TestFindMaximumInComparableSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMaximumInComparableSlice(c.input)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMaximumInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestFindMaximumInSlice_NonEmptySlice", []int{1, 2, 3}, 3},
+ {"TestFindMaximumInSlice_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMaximumInSlice(c.input, func(v int) int {
+ return v
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMin2MaxInComparableSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expectedMin int
+ expectedMax int
+ }{
+ {"TestFindMin2MaxInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3},
+ {"TestFindMin2MaxInComparableSlice_EmptySlice", []int{}, 0, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ minimum, maximum := collection.FindMin2MaxInComparableSlice(c.input)
+ if minimum != c.expectedMin || maximum != c.expectedMax {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMin2MaxInSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expectedMin int
+ expectedMax int
+ }{
+ {"TestFindMin2MaxInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3},
+ {"TestFindMin2MaxInSlice_EmptySlice", []int{}, 0, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ minimum, maximum := collection.FindMin2MaxInSlice(c.input, func(v int) int {
+ return v
+ })
+ if minimum != c.expectedMin || maximum != c.expectedMax {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMinFromComparableMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected int
+ }{
+ {"TestFindMinFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1},
+ {"TestFindMinFromComparableMap_EmptyMap", map[int]int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMinFromComparableMap(c.input)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMinFromMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected int
+ }{
+ {"TestFindMinFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1},
+ {"TestFindMinFromMap_EmptyMap", map[int]int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMinFromMap(c.input, func(v int) int {
+ return v
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMaxFromComparableMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected int
+ }{
+ {"TestFindMaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3},
+ {"TestFindMaxFromComparableMap_EmptyMap", map[int]int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMaxFromComparableMap(c.input)
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMaxFromMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected int
+ }{
+ {"TestFindMaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3},
+ {"TestFindMaxFromMap_EmptyMap", map[int]int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.FindMaxFromMap(c.input, func(v int) int {
+ return v
+ })
+ if actual != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMin2MaxFromComparableMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expectedMin int
+ expectedMax int
+ }{
+ {"TestFindMin2MaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3},
+ {"TestFindMin2MaxFromComparableMap_EmptyMap", map[int]int{}, 0, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ minimum, maximum := collection.FindMin2MaxFromComparableMap(c.input)
+ if minimum != c.expectedMin || maximum != c.expectedMax {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
+ }
+ })
+ }
+}
+
+func TestFindMin2MaxFromMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expectedMin int
+ expectedMax int
+ }{
+ {"TestFindMin2MaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3},
+ {"TestFindMin2MaxFromMap_EmptyMap", map[int]int{}, 0, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ minimum, maximum := collection.FindMin2MaxFromMap(c.input)
+ if minimum != c.expectedMin || maximum != c.expectedMax {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/item.go b/toolkit/collection/item.go
new file mode 100644
index 0000000..fc9fd40
--- /dev/null
+++ b/toolkit/collection/item.go
@@ -0,0 +1,9 @@
+package collection
+
+// SwapSlice 将切片中的两个元素进行交换
+func SwapSlice[S ~[]V, V any](slice *S, i, j int) {
+ if i < 0 || j < 0 || i >= len(*slice) || j >= len(*slice) {
+ return
+ }
+ (*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i]
+}
diff --git a/toolkit/collection/item_example_test.go b/toolkit/collection/item_example_test.go
new file mode 100644
index 0000000..277ee7a
--- /dev/null
+++ b/toolkit/collection/item_example_test.go
@@ -0,0 +1,14 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleSwapSlice() {
+ var s = []int{1, 2, 3}
+ collection.SwapSlice(&s, 0, 1)
+ fmt.Println(s)
+ // Output:
+ // [2 1 3]
+}
diff --git a/toolkit/collection/item_test.go b/toolkit/collection/item_test.go
new file mode 100644
index 0000000..faf36a3
--- /dev/null
+++ b/toolkit/collection/item_test.go
@@ -0,0 +1,32 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestSwapSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ slice []int
+ i int
+ j int
+ expect []int
+ }{
+ {"TestSwapSliceNonEmpty", []int{1, 2, 3}, 0, 1, []int{2, 1, 3}},
+ {"TestSwapSliceEmpty", []int{}, 0, 0, []int{}},
+ {"TestSwapSliceIndexOutOfBound", []int{1, 2, 3}, 0, 3, []int{1, 2, 3}},
+ {"TestSwapSliceNil", nil, 0, 0, nil},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.SwapSlice(&c.slice, c.i, c.j)
+ for i, v := range c.slice {
+ if v != c.expect[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expect, c.slice, "the slice is not equal")
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/listings/paged_slice.go b/toolkit/collection/listings/paged_slice.go
new file mode 100644
index 0000000..d58c29d
--- /dev/null
+++ b/toolkit/collection/listings/paged_slice.go
@@ -0,0 +1,68 @@
+package listings
+
+// NewPagedSlice 创建一个新的 PagedSlice 实例。
+func NewPagedSlice[T any](pageSize int) *PagedSlice[T] {
+ return &PagedSlice[T]{
+ pages: make([][]T, 0, pageSize),
+ pageSize: pageSize,
+ }
+}
+
+// PagedSlice 是一个高效的动态数组,它通过分页管理内存并减少频繁的内存分配来提高性能。
+type PagedSlice[T any] struct {
+ pages [][]T
+ pageSize int
+ len int
+ lenLast int
+}
+
+// Add 添加一个元素到 PagedSlice 中。
+func (slf *PagedSlice[T]) Add(value T) {
+ if slf.lenLast == len(slf.pages[len(slf.pages)-1]) {
+ slf.pages = append(slf.pages, make([]T, slf.pageSize))
+ slf.lenLast = 0
+ }
+
+ slf.pages[len(slf.pages)-1][slf.lenLast] = value
+ slf.len++
+ slf.lenLast++
+}
+
+// Get 获取 PagedSlice 中给定索引的元素。
+func (slf *PagedSlice[T]) Get(index int) *T {
+ if index < 0 || index >= slf.len {
+ return nil
+ }
+
+ return &slf.pages[index/slf.pageSize][index%slf.pageSize]
+}
+
+// Set 设置 PagedSlice 中给定索引的元素。
+func (slf *PagedSlice[T]) Set(index int, value T) {
+ if index < 0 || index >= slf.len {
+ return
+ }
+
+ slf.pages[index/slf.pageSize][index%slf.pageSize] = value
+}
+
+// Len 返回 PagedSlice 中元素的数量。
+func (slf *PagedSlice[T]) Len() int {
+ return slf.len
+}
+
+// Clear 清空 PagedSlice。
+func (slf *PagedSlice[T]) Clear() {
+ slf.pages = make([][]T, 0)
+ slf.len = 0
+ slf.lenLast = 0
+}
+
+// Range 迭代 PagedSlice 中的所有元素。
+func (slf *PagedSlice[T]) Range(f func(index int, value T) bool) {
+ for i := 0; i < slf.len; i++ {
+ if !f(i, slf.pages[i/slf.pageSize][i%slf.pageSize]) {
+ return
+ }
+ }
+}
diff --git a/toolkit/collection/listings/priority_slice.go b/toolkit/collection/listings/priority_slice.go
new file mode 100644
index 0000000..fe70d99
--- /dev/null
+++ b/toolkit/collection/listings/priority_slice.go
@@ -0,0 +1,207 @@
+package listings
+
+import (
+ "fmt"
+ "sort"
+)
+
+// NewPrioritySlice 创建一个优先级切片,优先级越低越靠前
+func NewPrioritySlice[V any](lengthAndCap ...int) *PrioritySlice[V] {
+ p := &PrioritySlice[V]{}
+ if len(lengthAndCap) > 0 {
+ var length = lengthAndCap[0]
+ var c int
+ if len(lengthAndCap) > 1 {
+ c = lengthAndCap[1]
+ }
+ p.items = make([]*priorityItem[V], length, c)
+ }
+ return p
+}
+
+// PrioritySlice 是一个优先级切片,优先级越低越靠前
+type PrioritySlice[V any] struct {
+ items []*priorityItem[V]
+}
+
+// Len 返回切片长度
+func (slf *PrioritySlice[V]) Len() int {
+ return len(slf.items)
+}
+
+// Cap 返回切片容量
+func (slf *PrioritySlice[V]) Cap() int {
+ return cap(slf.items)
+}
+
+// Clear 清空切片
+func (slf *PrioritySlice[V]) Clear() {
+ slf.items = slf.items[:0]
+}
+
+// Append 添加元素
+func (slf *PrioritySlice[V]) Append(v V, p int) {
+ slf.items = append(slf.items, &priorityItem[V]{
+ v: v,
+ p: p,
+ })
+ slf.sort()
+}
+
+// Appends 添加元素
+func (slf *PrioritySlice[V]) Appends(priority int, vs ...V) {
+ for _, v := range vs {
+ slf.Append(v, priority)
+ }
+ slf.sort()
+}
+
+// AppendByOptionalPriority 添加元素
+func (slf *PrioritySlice[V]) AppendByOptionalPriority(v V, priority ...int) {
+ if len(priority) == 0 {
+ slf.Append(v, 0)
+ } else {
+ slf.Append(v, priority[0])
+ }
+}
+
+// Get 获取元素
+func (slf *PrioritySlice[V]) Get(index int) (V, int) {
+ i := slf.items[index]
+ return i.Value(), i.Priority()
+}
+
+// GetValue 获取元素值
+func (slf *PrioritySlice[V]) GetValue(index int) V {
+ return slf.items[index].Value()
+}
+
+// GetPriority 获取元素优先级
+func (slf *PrioritySlice[V]) GetPriority(index int) int {
+ return slf.items[index].Priority()
+}
+
+// Set 设置元素
+func (slf *PrioritySlice[V]) Set(index int, value V, priority int) {
+ before := slf.items[index]
+ slf.items[index] = &priorityItem[V]{
+ v: value,
+ p: priority,
+ }
+ if before.Priority() != priority {
+ slf.sort()
+ }
+}
+
+// SetValue 设置元素值
+func (slf *PrioritySlice[V]) SetValue(index int, value V) {
+ slf.items[index].v = value
+}
+
+// SetPriority 设置元素优先级
+func (slf *PrioritySlice[V]) SetPriority(index int, priority int) {
+ slf.items[index].p = priority
+ slf.sort()
+}
+
+// Action 直接操作切片,如果返回值不为 nil,则替换切片
+func (slf *PrioritySlice[V]) Action(action func(items []*priorityItem[V]) []*priorityItem[V]) {
+ if len(slf.items) == 0 {
+ return
+ }
+ if replace := action(slf.items); replace != nil {
+ slf.items = replace
+ slf.sort()
+ }
+}
+
+// Range 遍历切片,如果返回值为 false,则停止遍历
+func (slf *PrioritySlice[V]) Range(action func(index int, item *priorityItem[V]) bool) {
+ for i, item := range slf.items {
+ if !action(i, item) {
+ break
+ }
+ }
+}
+
+// RangeValue 遍历切片值,如果返回值为 false,则停止遍历
+func (slf *PrioritySlice[V]) RangeValue(action func(index int, value V) bool) {
+ slf.Range(func(index int, item *priorityItem[V]) bool {
+ return action(index, item.Value())
+ })
+}
+
+// RangePriority 遍历切片优先级,如果返回值为 false,则停止遍历
+func (slf *PrioritySlice[V]) RangePriority(action func(index int, priority int) bool) {
+ slf.Range(func(index int, item *priorityItem[V]) bool {
+ return action(index, item.Priority())
+ })
+}
+
+// SyncSlice 返回切片
+func (slf *PrioritySlice[V]) Slice() []V {
+ var vs []V
+ for _, item := range slf.items {
+ vs = append(vs, item.Value())
+ }
+ return vs
+}
+
+// String 返回切片字符串
+func (slf *PrioritySlice[V]) String() string {
+ var vs []V
+ for _, item := range slf.items {
+ vs = append(vs, item.Value())
+ }
+ return fmt.Sprint(vs)
+}
+
+// sort 排序
+func (slf *PrioritySlice[V]) sort() {
+ if len(slf.items) <= 1 {
+ return
+ }
+ sort.Slice(slf.items, func(i, j int) bool {
+ return slf.items[i].Priority() < slf.items[j].Priority()
+ })
+ for i := 0; i < len(slf.items); i++ {
+ if i == 0 {
+ slf.items[i].prev = nil
+ slf.items[i].next = slf.items[i+1]
+ } else if i == len(slf.items)-1 {
+ slf.items[i].prev = slf.items[i-1]
+ slf.items[i].next = nil
+ } else {
+ slf.items[i].prev = slf.items[i-1]
+ slf.items[i].next = slf.items[i+1]
+ }
+ }
+}
+
+// priorityItem 是一个优先级切片元素
+type priorityItem[V any] struct {
+ next *priorityItem[V]
+ prev *priorityItem[V]
+ v V
+ p int
+}
+
+// Value 返回元素值
+func (p *priorityItem[V]) Value() V {
+ return p.v
+}
+
+// Priority 返回元素优先级
+func (p *priorityItem[V]) Priority() int {
+ return p.p
+}
+
+// Next 返回下一个元素
+func (p *priorityItem[V]) Next() *priorityItem[V] {
+ return p.next
+}
+
+// Prev 返回上一个元素
+func (p *priorityItem[V]) Prev() *priorityItem[V] {
+ return p.prev
+}
diff --git a/toolkit/collection/listings/priority_slice_test.go b/toolkit/collection/listings/priority_slice_test.go
new file mode 100644
index 0000000..c8d3ac8
--- /dev/null
+++ b/toolkit/collection/listings/priority_slice_test.go
@@ -0,0 +1,14 @@
+package listings_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection/listings"
+ "testing"
+)
+
+func TestPrioritySlice_Append(t *testing.T) {
+ var s = listings.NewPrioritySlice[string]()
+ s.Append("name_1", 2)
+ s.Append("name_2", 1)
+ fmt.Println(s)
+}
diff --git a/toolkit/collection/listings/sync_priority_slice.go b/toolkit/collection/listings/sync_priority_slice.go
new file mode 100644
index 0000000..b1b5083
--- /dev/null
+++ b/toolkit/collection/listings/sync_priority_slice.go
@@ -0,0 +1,209 @@
+package listings
+
+import (
+ "fmt"
+ "sort"
+ "sync"
+)
+
+// NewSyncPrioritySlice 创建一个并发安全的优先级切片,优先级越低越靠前
+func NewSyncPrioritySlice[V any](lengthAndCap ...int) *SyncPrioritySlice[V] {
+ p := &SyncPrioritySlice[V]{}
+ if len(lengthAndCap) > 0 {
+ var length = lengthAndCap[0]
+ var c int
+ if len(lengthAndCap) > 1 {
+ c = lengthAndCap[1]
+ }
+ p.items = make([]*priorityItem[V], length, c)
+ }
+ return p
+}
+
+// SyncPrioritySlice 是一个优先级切片,优先级越低越靠前
+type SyncPrioritySlice[V any] struct {
+ rw sync.RWMutex
+ items []*priorityItem[V]
+}
+
+// Len 返回切片长度
+func (slf *SyncPrioritySlice[V]) Len() int {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ return len(slf.items)
+}
+
+// Cap 返回切片容量
+func (slf *SyncPrioritySlice[V]) Cap() int {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ return cap(slf.items)
+}
+
+// Clear 清空切片
+func (slf *SyncPrioritySlice[V]) Clear() {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ slf.items = slf.items[:0]
+}
+
+// Append 添加元素
+func (slf *SyncPrioritySlice[V]) Append(v V, p int) {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ slf.items = append(slf.items, &priorityItem[V]{
+ v: v,
+ p: p,
+ })
+ slf.sort()
+}
+
+// Appends 添加元素
+func (slf *SyncPrioritySlice[V]) Appends(priority int, vs ...V) {
+ for _, v := range vs {
+ slf.Append(v, priority)
+ }
+ slf.sort()
+}
+
+// AppendByOptionalPriority 添加元素
+func (slf *SyncPrioritySlice[V]) AppendByOptionalPriority(v V, priority ...int) {
+ if len(priority) == 0 {
+ slf.Append(v, 0)
+ } else {
+ slf.Append(v, priority[0])
+ }
+}
+
+// Get 获取元素
+func (slf *SyncPrioritySlice[V]) Get(index int) (V, int) {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ i := slf.items[index]
+ return i.Value(), i.Priority()
+}
+
+// GetValue 获取元素值
+func (slf *SyncPrioritySlice[V]) GetValue(index int) V {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ return slf.items[index].Value()
+}
+
+// GetPriority 获取元素优先级
+func (slf *SyncPrioritySlice[V]) GetPriority(index int) int {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ return slf.items[index].Priority()
+}
+
+// Set 设置元素
+func (slf *SyncPrioritySlice[V]) Set(index int, value V, priority int) {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ before := slf.items[index]
+ slf.items[index] = &priorityItem[V]{
+ v: value,
+ p: priority,
+ }
+ if before.Priority() != priority {
+ slf.sort()
+ }
+}
+
+// SetValue 设置元素值
+func (slf *SyncPrioritySlice[V]) SetValue(index int, value V) {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ slf.items[index].v = value
+}
+
+// SetPriority 设置元素优先级
+func (slf *SyncPrioritySlice[V]) SetPriority(index int, priority int) {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ slf.items[index].p = priority
+ slf.sort()
+}
+
+// Action 直接操作切片,如果返回值不为 nil,则替换切片
+func (slf *SyncPrioritySlice[V]) Action(action func(items []*priorityItem[V]) []*priorityItem[V]) {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ if len(slf.items) == 0 {
+ return
+ }
+ if replace := action(slf.items); replace != nil {
+ slf.items = replace
+ slf.sort()
+ }
+}
+
+// Range 遍历切片,如果返回值为 false,则停止遍历
+func (slf *SyncPrioritySlice[V]) Range(action func(index int, item *priorityItem[V]) bool) {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ for i, item := range slf.items {
+ if !action(i, item) {
+ break
+ }
+ }
+}
+
+// RangeValue 遍历切片值,如果返回值为 false,则停止遍历
+func (slf *SyncPrioritySlice[V]) RangeValue(action func(index int, value V) bool) {
+ slf.Range(func(index int, item *priorityItem[V]) bool {
+ return action(index, item.Value())
+ })
+}
+
+// RangePriority 遍历切片优先级,如果返回值为 false,则停止遍历
+func (slf *SyncPrioritySlice[V]) RangePriority(action func(index int, priority int) bool) {
+ slf.Range(func(index int, item *priorityItem[V]) bool {
+ return action(index, item.Priority())
+ })
+}
+
+// Slice 返回切片
+func (slf *SyncPrioritySlice[V]) Slice() []V {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ var vs []V
+ for _, item := range slf.items {
+ vs = append(vs, item.Value())
+ }
+ return vs
+}
+
+// String 返回切片字符串
+func (slf *SyncPrioritySlice[V]) String() string {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ var vs []V
+ for _, item := range slf.items {
+ vs = append(vs, item.Value())
+ }
+ return fmt.Sprint(vs)
+}
+
+// sort 排序
+func (slf *SyncPrioritySlice[V]) sort() {
+ if len(slf.items) <= 1 {
+ return
+ }
+ sort.Slice(slf.items, func(i, j int) bool {
+ return slf.items[i].Priority() < slf.items[j].Priority()
+ })
+ for i := 0; i < len(slf.items); i++ {
+ if i == 0 {
+ slf.items[i].prev = nil
+ slf.items[i].next = slf.items[i+1]
+ } else if i == len(slf.items)-1 {
+ slf.items[i].prev = slf.items[i-1]
+ slf.items[i].next = nil
+ } else {
+ slf.items[i].prev = slf.items[i-1]
+ slf.items[i].next = slf.items[i+1]
+ }
+ }
+}
diff --git a/toolkit/collection/listings/sync_slice.go b/toolkit/collection/listings/sync_slice.go
new file mode 100644
index 0000000..3299289
--- /dev/null
+++ b/toolkit/collection/listings/sync_slice.go
@@ -0,0 +1,61 @@
+package listings
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "sync"
+)
+
+// NewSyncSlice 创建一个 SyncSlice
+func NewSyncSlice[V any](length, cap int) *SyncSlice[V] {
+ s := &SyncSlice[V]{}
+ if length > 0 || cap > 0 {
+ s.data = make([]V, length, cap)
+ }
+ return s
+}
+
+// SyncSlice 是基于 sync.RWMutex 实现的线程安全的 slice
+type SyncSlice[V any] struct {
+ rw sync.RWMutex
+ data []V
+}
+
+func (slf *SyncSlice[V]) Get(index int) V {
+ slf.rw.RLock()
+ defer slf.rw.RUnlock()
+ return slf.data[index]
+}
+
+func (slf *SyncSlice[V]) GetWithRange(start, end int) []V {
+ return slf.data[start:end]
+}
+
+func (slf *SyncSlice[V]) Set(index int, value V) {
+ slf.rw.Lock()
+ slf.data[index] = value
+ slf.rw.Unlock()
+}
+
+func (slf *SyncSlice[V]) Append(values ...V) {
+ slf.rw.Lock()
+ slf.data = append(slf.data, values...)
+ slf.rw.Unlock()
+}
+
+func (slf *SyncSlice[V]) Release() {
+ slf.rw.Lock()
+ slf.data = nil
+ slf.rw.Unlock()
+}
+
+func (slf *SyncSlice[V]) Clear() {
+ slf.rw.Lock()
+ slf.data = slf.data[:0]
+ slf.rw.Unlock()
+}
+
+func (slf *SyncSlice[V]) GetData() []V {
+ slf.rw.Lock()
+ defer slf.rw.Unlock()
+ return collection.CloneSlice(slf.data)
+}
diff --git a/toolkit/collection/loop.go b/toolkit/collection/loop.go
new file mode 100644
index 0000000..9b3700c
--- /dev/null
+++ b/toolkit/collection/loop.go
@@ -0,0 +1,188 @@
+package collection
+
+import (
+ "github.com/kercylan98/minotaur/utils/generic"
+ "sort"
+)
+
+// LoopSlice 迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopSlice[S ~[]V, V any](slice S, f func(i int, val V) bool) {
+ for i, v := range slice {
+ if !f(i, v) {
+ break
+ }
+ }
+}
+
+// ReverseLoopSlice 逆序迭代切片 slice 中的每一个函数,并将索引和值传递给 f 函数
+// - 迭代过程将在 f 函数返回 false 时中断
+func ReverseLoopSlice[S ~[]V, V any](slice S, f func(i int, val V) bool) {
+ for i := len(slice) - 1; i >= 0; i-- {
+ if !f(i, slice[i]) {
+ break
+ }
+ }
+}
+
+// LoopMap 迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - m 的迭代顺序是不确定的,因此每次迭代的顺序可能不同
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMap[M ~map[K]V, K comparable, V any](m M, f func(i int, key K, val V) bool) {
+ var i int
+ for k, v := range m {
+ if !f(i, k, v) {
+ break
+ }
+ i++
+ }
+}
+
+// LoopMapByOrderedKeyAsc 按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByOrderedKeyAsc[M ~map[K]V, K generic.Ordered, V any](m M, f func(i int, key K, val V) bool) {
+ var keys []K
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return AscBy(keys[i], keys[j])
+ })
+ for i, k := range keys {
+ if !f(i, k, m[k]) {
+ break
+ }
+ }
+}
+
+// LoopMapByOrderedKeyDesc 按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByOrderedKeyDesc[M ~map[K]V, K generic.Ordered, V any](m M, f func(i int, key K, val V) bool) {
+ var keys []K
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return DescBy(keys[i], keys[j])
+ })
+ for i, k := range keys {
+ if !f(i, k, m[k]) {
+ break
+ }
+ }
+}
+
+// LoopMapByOrderedValueAsc 按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByOrderedValueAsc[M ~map[K]V, K comparable, V generic.Ordered](m M, f func(i int, key K, val V) bool) {
+ var keys []K
+ var values []V
+ for k, v := range m {
+ keys = append(keys, k)
+ values = append(values, v)
+ }
+ sort.Slice(values, func(i, j int) bool {
+ return AscBy(values[i], values[j])
+ })
+ for i, v := range values {
+ if !f(i, keys[i], v) {
+ break
+ }
+ }
+}
+
+// LoopMapByOrderedValueDesc 按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByOrderedValueDesc[M ~map[K]V, K comparable, V generic.Ordered](m M, f func(i int, key K, val V) bool) {
+ var keys []K
+ var values []V
+ for k, v := range m {
+ keys = append(keys, k)
+ values = append(values, v)
+ }
+ sort.Slice(values, func(i, j int) bool {
+ return DescBy(values[i], values[j])
+ })
+ for i, v := range values {
+ if !f(i, keys[i], v) {
+ break
+ }
+ }
+}
+
+// LoopMapByKeyGetterAsc 按照键的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByKeyGetterAsc[M ~map[K]V, K comparable, V comparable, N generic.Ordered](m M, getter func(k K) N, f func(i int, key K, val V) bool) {
+ var keys []K
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return AscBy(getter(keys[i]), getter(keys[j]))
+ })
+ for i, v := range keys {
+ if !f(i, keys[i], m[v]) {
+ break
+ }
+ }
+}
+
+// LoopMapByValueGetterAsc 按照值的升序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByValueGetterAsc[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, getter func(v V) N, f func(i int, key K, val V) bool) {
+ var keys []K
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return AscBy(getter(m[keys[i]]), getter(m[keys[j]]))
+ })
+ for i, v := range keys {
+ if !f(i, keys[i], m[v]) {
+ break
+ }
+ }
+}
+
+// LoopMapByKeyGetterDesc 按照键的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByKeyGetterDesc[M ~map[K]V, K comparable, V comparable, N generic.Ordered](m M, getter func(k K) N, f func(i int, key K, val V) bool) {
+ var keys []K
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return DescBy(getter(keys[i]), getter(keys[j]))
+ })
+ for i, v := range keys {
+ if !f(i, keys[i], m[v]) {
+ break
+ }
+ }
+}
+
+// LoopMapByValueGetterDesc 按照值的降序迭代 m 中的每一个函数,并将键和值传递给 f 函数
+// - 该函数会在 f 中传入一个从 0 开始的索引,用于表示当前迭代的次数
+// - 迭代过程将在 f 函数返回 false 时中断
+func LoopMapByValueGetterDesc[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, getter func(v V) N, f func(i int, key K, val V) bool) {
+ var keys []K
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return DescBy(getter(m[keys[i]]), getter(m[keys[j]]))
+ })
+ for i, v := range keys {
+ if !f(i, keys[i], m[v]) {
+ break
+ }
+ }
+}
diff --git a/toolkit/collection/loop_example_test.go b/toolkit/collection/loop_example_test.go
new file mode 100644
index 0000000..f725606
--- /dev/null
+++ b/toolkit/collection/loop_example_test.go
@@ -0,0 +1,159 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleLoopSlice() {
+ var result []int
+ collection.LoopSlice([]int{1, 2, 3, 4, 5}, func(i int, val int) bool {
+ result = append(result, val)
+ if uint(i) == 1 {
+ return false
+ }
+ return true
+ })
+ fmt.Println(result)
+ // Output: [1 2]
+}
+
+func ExampleReverseLoopSlice() {
+ var result []int
+ collection.ReverseLoopSlice([]int{1, 2, 3, 4, 5}, func(i int, val int) bool {
+ result = append(result, val)
+ if uint(i) == 1 {
+ return false
+ }
+ return true
+ })
+ fmt.Println(result)
+ // Output: [5 4 3 2]
+}
+
+func ExampleLoopMap() {
+ var result []int
+ collection.LoopMap(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ })
+ fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByOrderedKeyAsc() {
+ var result []int
+ collection.LoopMapByOrderedKeyAsc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ })
+ fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByOrderedKeyDesc() {
+ var result []int
+ collection.LoopMapByOrderedKeyDesc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ })
+ fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByOrderedValueAsc() {
+ var result []int
+ collection.LoopMapByOrderedValueAsc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ })
+ fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByOrderedValueDesc() {
+ var result []int
+ collection.LoopMapByOrderedValueDesc(map[string]int{"a": 1, "b": 2, "c": 3}, func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ })
+ fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByKeyGetterAsc() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result []int
+ collection.LoopMapByKeyGetterAsc(
+ m,
+ func(k string) int {
+ return m[k]
+ },
+ func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ },
+ )
+ fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByKeyGetterDesc() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result []int
+ collection.LoopMapByKeyGetterDesc(
+ m,
+ func(k string) int {
+ return m[k]
+ },
+ func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ },
+ )
+ fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByValueGetterAsc() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result []int
+ collection.LoopMapByValueGetterAsc(
+ m,
+ func(v int) int {
+ return v
+ },
+ func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ },
+ )
+ fmt.Println(collection.AllInComparableSlice(result, []int{1, 2, 3}))
+ // Output:
+ // true
+}
+
+func ExampleLoopMapByValueGetterDesc() {
+ var m = map[string]int{"a": 1, "b": 2, "c": 3}
+ var result []int
+ collection.LoopMapByValueGetterDesc(
+ m,
+ func(v int) int {
+ return v
+ },
+ func(i int, key string, val int) bool {
+ result = append(result, val)
+ return true
+ },
+ )
+ fmt.Println(collection.AllInComparableSlice(result, []int{3, 2, 1}))
+ // Output:
+ // true
+}
diff --git a/toolkit/collection/loop_test.go b/toolkit/collection/loop_test.go
new file mode 100644
index 0000000..8c0c739
--- /dev/null
+++ b/toolkit/collection/loop_test.go
@@ -0,0 +1,333 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestLoopSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ in []int
+ out []int
+ breakIndex uint
+ }{
+ {"TestLoopSlice_Part", []int{1, 2, 3, 4, 5}, []int{1, 2}, 2},
+ {"TestLoopSlice_All", []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}, 0},
+ {"TestLoopSlice_Empty", []int{}, []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []int
+ collection.LoopSlice(c.in, func(i int, val int) bool {
+ result = append(result, val)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopSlice(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestReverseLoopSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ in []int
+ out []int
+ breakIndex uint
+ }{
+ {"TestReverseLoopSlice_Part", []int{1, 2, 3, 4, 5}, []int{5, 4}, 2},
+ {"TestReverseLoopSlice_All", []int{1, 2, 3, 4, 5}, []int{5, 4, 3, 2, 1}, 0},
+ {"TestReverseLoopSlice_Empty", []int{}, []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []int
+ collection.ReverseLoopSlice(c.in, func(i int, val int) bool {
+ result = append(result, val)
+ if c.breakIndex != 0 && uint(i) == uint(len(c.in))-c.breakIndex {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("ReverseLoopSlice(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out map[int]string
+ breakIndex uint
+ }{
+ {"TestLoopMap_Part", map[int]string{1: "1", 2: "2", 3: "3"}, map[int]string{1: "1", 2: "2"}, 2},
+ {"TestLoopMap_All", map[int]string{1: "1", 2: "2", 3: "3"}, map[int]string{1: "1", 2: "2", 3: "3"}, 0},
+ {"TestLoopMap_Empty", map[int]string{}, map[int]string{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result = make(map[int]string)
+ collection.LoopMap(c.in, func(i int, key int, val string) bool {
+ result[key] = val
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableMap(result, c.out) {
+ t.Errorf("LoopMap(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByOrderedKeyAsc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []int
+ breakIndex uint
+ }{
+ {"TestLoopMapByOrderedKeyAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2}, 2},
+ {"TestLoopMapByOrderedKeyAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2, 3}, 0},
+ {"TestLoopMapByOrderedKeyAsc_Empty", map[int]string{}, []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []int
+ collection.LoopMapByOrderedKeyAsc(c.in, func(i int, key int, val string) bool {
+ result = append(result, key)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByOrderedKeyAsc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByOrderedKeyDesc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []int
+ breakIndex uint
+ }{
+ {"TestLoopMapByOrderedKeyDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2}, 2},
+ {"TestLoopMapByOrderedKeyDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2, 1}, 0},
+ {"TestLoopMapByOrderedKeyDesc_Empty", map[int]string{}, []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []int
+ collection.LoopMapByOrderedKeyDesc(c.in, func(i int, key int, val string) bool {
+ result = append(result, key)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByOrderedKeyDesc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByOrderedValueAsc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []string
+ breakIndex uint
+ }{
+ {"TestLoopMapByOrderedValueAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2"}, 2},
+ {"TestLoopMapByOrderedValueAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2", "3"}, 0},
+ {"TestLoopMapByOrderedValueAsc_Empty", map[int]string{}, []string{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []string
+ collection.LoopMapByOrderedValueAsc(c.in, func(i int, key int, val string) bool {
+ result = append(result, val)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByOrderedValueAsc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByOrderedValueDesc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []string
+ breakIndex uint
+ }{
+ {"TestLoopMapByOrderedValueDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2"}, 2},
+ {"TestLoopMapByOrderedValueDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2", "1"}, 0},
+ {"TestLoopMapByOrderedValueDesc_Empty", map[int]string{}, []string{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []string
+ collection.LoopMapByOrderedValueDesc(c.in, func(i int, key int, val string) bool {
+ result = append(result, val)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByOrderedValueDesc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByKeyGetterAsc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []int
+ breakIndex uint
+ }{
+ {"TestLoopMapByKeyGetterAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2}, 2},
+ {"TestLoopMapByKeyGetterAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{1, 2, 3}, 0},
+ {"TestLoopMapByKeyGetterAsc_Empty", map[int]string{}, []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []int
+ collection.LoopMapByKeyGetterAsc(c.in, func(key int) int {
+ return key
+ }, func(i int, key int, val string) bool {
+ result = append(result, key)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByKeyGetterAsc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByKeyGetterDesc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []int
+ breakIndex uint
+ }{
+ {"TestLoopMapByKeyGetterDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2}, 2},
+ {"TestLoopMapByKeyGetterDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []int{3, 2, 1}, 0},
+ {"TestLoopMapByKeyGetterDesc_Empty", map[int]string{}, []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []int
+ collection.LoopMapByKeyGetterDesc(c.in, func(key int) int {
+ return key
+ }, func(i int, key int, val string) bool {
+ result = append(result, key)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByKeyGetterDesc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByValueGetterAsc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []string
+ breakIndex uint
+ }{
+ {"TestLoopMapByValueGetterAsc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2"}, 2},
+ {"TestLoopMapByValueGetterAsc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"1", "2", "3"}, 0},
+ {"TestLoopMapByValueGetterAsc_Empty", map[int]string{}, []string{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []string
+ collection.LoopMapByValueGetterAsc(c.in, func(val string) string {
+ return val
+ }, func(i int, key int, val string) bool {
+ result = append(result, val)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByValueGetterAsc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
+
+func TestLoopMapByValueGetterDesc(t *testing.T) {
+ var cases = []struct {
+ name string
+ in map[int]string
+ out []string
+ breakIndex uint
+ }{
+ {"TestLoopMapByValueGetterDesc_Part", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2"}, 2},
+ {"TestLoopMapByValueGetterDesc_All", map[int]string{1: "1", 2: "2", 3: "3"}, []string{"3", "2", "1"}, 0},
+ {"TestLoopMapByValueGetterDesc_Empty", map[int]string{}, []string{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ var result []string
+ collection.LoopMapByValueGetterDesc(c.in, func(val string) string {
+ return val
+ }, func(i int, key int, val string) bool {
+ result = append(result, val)
+ if c.breakIndex != 0 && uint(i) == c.breakIndex-1 {
+ return false
+ }
+ return true
+ })
+ if !collection.EqualComparableSlice(result, c.out) {
+ t.Errorf("LoopMapByValueGetterDesc(%v) got %v, want %v", c.in, result, c.out)
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/map.go b/toolkit/collection/map.go
new file mode 100644
index 0000000..6a8a72b
--- /dev/null
+++ b/toolkit/collection/map.go
@@ -0,0 +1,25 @@
+package collection
+
+// MappingFromSlice 将切片中的元素进行转换
+func MappingFromSlice[S ~[]V, NS []N, V, N any](slice S, handler func(value V) N) NS {
+ if slice == nil {
+ return nil
+ }
+ result := make(NS, len(slice))
+ for i, v := range slice {
+ result[i] = handler(v)
+ }
+ return result
+}
+
+// MappingFromMap 将 map 中的元素进行转换
+func MappingFromMap[M ~map[K]V, NM map[K]N, K comparable, V, N any](m M, handler func(value V) N) NM {
+ if m == nil {
+ return nil
+ }
+ result := make(NM, len(m))
+ for k, v := range m {
+ result[k] = handler(v)
+ }
+ return result
+}
diff --git a/toolkit/collection/map_example_test.go b/toolkit/collection/map_example_test.go
new file mode 100644
index 0000000..d26b712
--- /dev/null
+++ b/toolkit/collection/map_example_test.go
@@ -0,0 +1,24 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleMappingFromSlice() {
+ result := collection.MappingFromSlice([]int{1, 2, 3}, func(value int) int {
+ return value + 1
+ })
+ fmt.Println(result)
+ // Output:
+ // [2 3 4]
+}
+
+func ExampleMappingFromMap() {
+ result := collection.MappingFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(value int) int {
+ return value + 1
+ })
+ fmt.Println(result)
+ // Output:
+ // map[1:2 2:3 3:4]
+}
diff --git a/toolkit/collection/map_test.go b/toolkit/collection/map_test.go
new file mode 100644
index 0000000..163b598
--- /dev/null
+++ b/toolkit/collection/map_test.go
@@ -0,0 +1,60 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestMappingFromSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestMappingFromSlice_NonEmptySlice", []int{1, 2, 3}, []int{2, 3, 4}},
+ {"TestMappingFromSlice_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.MappingFromSlice[[]int, []int](c.input, func(value int) int {
+ return value + 1
+ })
+ if len(result) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ for i := 0; i < len(result); i++ {
+ if result[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestMappingFromMap(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]int
+ }{
+ {"TestMappingFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 2, 2: 3, 3: 4}},
+ {"TestMappingFromMap_EmptyMap", map[int]int{}, map[int]int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.MappingFromMap[map[int]int, map[int]int](c.input, func(value int) int {
+ return value + 1
+ })
+ if len(result) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ for k, v := range result {
+ if v != c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/mappings/sync_map.go b/toolkit/collection/mappings/sync_map.go
new file mode 100644
index 0000000..6da93c0
--- /dev/null
+++ b/toolkit/collection/mappings/sync_map.go
@@ -0,0 +1,221 @@
+package mappings
+
+import (
+ "encoding/json"
+ "github.com/kercylan98/minotaur/utils/collection"
+ "sync"
+)
+
+// NewSyncMap 创建一个 SyncMap
+func NewSyncMap[K comparable, V any](source ...map[K]V) *SyncMap[K, V] {
+ m := &SyncMap[K, V]{
+ data: make(map[K]V),
+ }
+ if len(source) > 0 {
+ m.data = collection.MergeMaps(source...)
+ }
+ return m
+}
+
+// SyncMap 是基于 sync.RWMutex 实现的线程安全的 map
+// - 适用于要考虑并发读写但是并发读写的频率不高的情况
+type SyncMap[K comparable, V any] struct {
+ lock sync.RWMutex
+ data map[K]V
+ atom bool
+}
+
+// Set 设置一个值
+func (sm *SyncMap[K, V]) Set(key K, value V) {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ sm.data[key] = value
+}
+
+// Get 获取一个值
+func (sm *SyncMap[K, V]) Get(key K) V {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ return sm.data[key]
+}
+
+// Atom 原子操作
+func (sm *SyncMap[K, V]) Atom(handle func(m map[K]V)) {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ handle(sm.data)
+}
+
+// Exist 判断是否存在
+func (sm *SyncMap[K, V]) Exist(key K) bool {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ _, exist := sm.data[key]
+ return exist
+}
+
+// GetExist 获取一个值并判断是否存在
+func (sm *SyncMap[K, V]) GetExist(key K) (V, bool) {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ value, exist := sm.data[key]
+ return value, exist
+}
+
+// Delete 删除一个值
+func (sm *SyncMap[K, V]) Delete(key K) {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ delete(sm.data, key)
+}
+
+// DeleteGet 删除一个值并返回
+func (sm *SyncMap[K, V]) DeleteGet(key K) V {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ v := sm.data[key]
+ delete(sm.data, key)
+ return v
+}
+
+// DeleteGetExist 删除一个值并返回是否存在
+func (sm *SyncMap[K, V]) DeleteGetExist(key K) (V, bool) {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ v, exist := sm.data[key]
+ delete(sm.data, key)
+ return v, exist
+}
+
+// DeleteExist 删除一个值并返回是否存在
+func (sm *SyncMap[K, V]) DeleteExist(key K) bool {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ if _, exist := sm.data[key]; !exist {
+ sm.lock.Unlock()
+ return exist
+ }
+ delete(sm.data, key)
+ return true
+}
+
+// Clear 清空
+func (sm *SyncMap[K, V]) Clear() {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ for k := range sm.data {
+ delete(sm.data, k)
+ }
+}
+
+// ClearHandle 清空并处理
+func (sm *SyncMap[K, V]) ClearHandle(handle func(key K, value V)) {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ for k, v := range sm.data {
+ handle(k, v)
+ delete(sm.data, k)
+ }
+}
+
+// Range 遍历所有值,如果 handle 返回 true 则停止遍历
+func (sm *SyncMap[K, V]) Range(handle func(key K, value V) bool) {
+ if !sm.atom {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ }
+ for k, v := range sm.data {
+ key, value := k, v
+ if handle(key, value) {
+ break
+ }
+ }
+}
+
+// Keys 获取所有的键
+func (sm *SyncMap[K, V]) Keys() []K {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ var s = make([]K, 0, len(sm.data))
+ for k := range sm.data {
+ s = append(s, k)
+ }
+ return s
+}
+
+// Slice 获取所有的值
+func (sm *SyncMap[K, V]) Slice() []V {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ var s = make([]V, 0, len(sm.data))
+ for _, v := range sm.data {
+ s = append(s, v)
+ }
+ return s
+}
+
+// Map 转换为普通 map
+func (sm *SyncMap[K, V]) Map() map[K]V {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ var m = make(map[K]V)
+ for k, v := range sm.data {
+ m[k] = v
+ }
+ return m
+}
+
+// Size 获取数量
+func (sm *SyncMap[K, V]) Size() int {
+ if !sm.atom {
+ sm.lock.RLock()
+ defer sm.lock.RUnlock()
+ }
+ return len(sm.data)
+}
+
+func (sm *SyncMap[K, V]) MarshalJSON() ([]byte, error) {
+ m := sm.Map()
+ return json.Marshal(m)
+}
+
+func (sm *SyncMap[K, V]) UnmarshalJSON(bytes []byte) error {
+ var m = make(map[K]V)
+ if !sm.atom {
+ sm.lock.Lock()
+ sm.lock.Unlock()
+ }
+ if err := json.Unmarshal(bytes, &m); err != nil {
+ return err
+ }
+ sm.data = m
+ return nil
+}
diff --git a/toolkit/collection/merge.go b/toolkit/collection/merge.go
new file mode 100644
index 0000000..1d6ee50
--- /dev/null
+++ b/toolkit/collection/merge.go
@@ -0,0 +1,67 @@
+package collection
+
+// MergeSlice 合并切片
+func MergeSlice[V any](values ...V) (result []V) {
+ if len(values) == 0 {
+ return nil
+ }
+
+ result = make([]V, 0, len(values))
+ result = append(result, values...)
+ return
+}
+
+// MergeSlices 合并切片
+func MergeSlices[S ~[]V, V any](slices ...S) (result S) {
+ if len(slices) == 0 {
+ return nil
+ }
+
+ var length int
+ for _, slice := range slices {
+ length += len(slice)
+ }
+
+ result = make(S, 0, length)
+ for _, slice := range slices {
+ result = append(result, slice...)
+ }
+ return
+}
+
+// MergeMaps 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key
+func MergeMaps[M ~map[K]V, K comparable, V any](maps ...M) (result M) {
+ if len(maps) == 0 {
+ return make(M)
+ }
+
+ var length int
+ for _, m := range maps {
+ length += len(m)
+ }
+
+ result = make(M, length)
+ for _, m := range maps {
+ for k, v := range m {
+ result[k] = v
+ }
+ }
+ return
+}
+
+// MergeMapsWithSkip 合并 map,当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会被跳过
+func MergeMapsWithSkip[M ~map[K]V, K comparable, V any](maps ...M) (result M) {
+ if len(maps) == 0 {
+ return nil
+ }
+
+ result = make(M)
+ for _, m := range maps {
+ for k, v := range m {
+ if _, ok := result[k]; !ok {
+ result[k] = v
+ }
+ }
+ }
+ return
+}
diff --git a/toolkit/collection/merge_example_test.go b/toolkit/collection/merge_example_test.go
new file mode 100644
index 0000000..bf34c43
--- /dev/null
+++ b/toolkit/collection/merge_example_test.go
@@ -0,0 +1,43 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleMergeSlice() {
+ fmt.Println(collection.MergeSlice(1, 2, 3))
+ // Output:
+ // [1 2 3]
+}
+
+func ExampleMergeSlices() {
+ fmt.Println(
+ collection.MergeSlices(
+ []int{1, 2, 3},
+ []int{4, 5, 6},
+ ),
+ )
+ // Output:
+ // [1 2 3 4 5 6]
+}
+
+func ExampleMergeMaps() {
+ m := collection.MergeMaps(
+ map[int]int{1: 1, 2: 2, 3: 3},
+ map[int]int{4: 4, 5: 5, 6: 6},
+ )
+ fmt.Println(len(m))
+ // Output:
+ // 6
+}
+
+func ExampleMergeMapsWithSkip() {
+ m := collection.MergeMapsWithSkip(
+ map[int]int{1: 1},
+ map[int]int{1: 2},
+ )
+ fmt.Println(m[1])
+ // Output:
+ // 1
+}
diff --git a/toolkit/collection/merge_test.go b/toolkit/collection/merge_test.go
new file mode 100644
index 0000000..46ce9be
--- /dev/null
+++ b/toolkit/collection/merge_test.go
@@ -0,0 +1,96 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestMergeSlice(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestMergeSlice_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
+ {"TestMergeSlice_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.MergeSlice(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 TestMergeSlices(t *testing.T) {
+ var cases = []struct {
+ name string
+ input [][]int
+ expected []int
+ }{
+ {"TestMergeSlices_NonEmptySlice", [][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2, 3, 4, 5, 6}},
+ {"TestMergeSlices_EmptySlice", [][]int{{}, {}}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.MergeSlices(c.input...)
+ if len(result) != len(c.expected) {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ for i := 0; i < len(result); i++ {
+ if result[i] != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
+ }
+ }
+ })
+ }
+}
+
+func TestMergeMaps(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []map[int]int
+ expected int
+ }{
+ {"TestMergeMaps_NonEmptyMap", []map[int]int{{1: 1, 2: 2, 3: 3}, {4: 4, 5: 5, 6: 6}}, 6},
+ {"TestMergeMaps_EmptyMap", []map[int]int{{}, {}}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.MergeMaps(c.input...)
+ if len(result) != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestMergeMapsWithSkip(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []map[int]int
+ expected int
+ }{
+ {"TestMergeMapsWithSkip_NonEmptyMap", []map[int]int{{1: 1}, {1: 2}}, 1},
+ {"TestMergeMapsWithSkip_EmptyMap", []map[int]int{{}, {}}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.MergeMapsWithSkip(c.input...)
+ if len(result) != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/random.go b/toolkit/collection/random.go
new file mode 100644
index 0000000..03e43fe
--- /dev/null
+++ b/toolkit/collection/random.go
@@ -0,0 +1,236 @@
+package collection
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/random"
+)
+
+// ChooseRandomSliceElementRepeatN 返回 slice 中的 n 个可重复随机元素
+// - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil
+func ChooseRandomSliceElementRepeatN[S ~[]V, V any](slice S, n int) (result []V) {
+ if len(slice) == 0 || n <= 0 {
+ return
+ }
+ result = make([]V, n)
+ m := len(slice) - 1
+ for i := 0; i < n; i++ {
+ result[i] = slice[random.Int(0, m)]
+ }
+ return
+}
+
+// ChooseRandomIndexRepeatN 返回 slice 中的 n 个可重复随机元素的索引
+// - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil
+func ChooseRandomIndexRepeatN[S ~[]V, V any](slice S, n int) (result []int) {
+ if len(slice) == 0 || n <= 0 {
+ return
+ }
+ result = make([]int, n)
+ m := len(slice) - 1
+ for i := 0; i < n; i++ {
+ result[i] = random.Int(0, m)
+ }
+ return
+}
+
+// ChooseRandomSliceElement 返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值
+func ChooseRandomSliceElement[S ~[]V, V any](slice S) (v V) {
+ if len(slice) == 0 {
+ return
+ }
+ return slice[random.Int(0, len(slice)-1)]
+}
+
+// ChooseRandomIndex 返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1
+func ChooseRandomIndex[S ~[]V, V any](slice S) (index int) {
+ if len(slice) == 0 {
+ return -1
+ }
+ return random.Int(0, len(slice)-1)
+}
+
+// ChooseRandomSliceElementN 返回 slice 中的 n 个不可重复的随机元素
+// - 当 slice 长度为 0 或 n 大于 slice 长度或小于 0 时将会发生 panic
+func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) {
+ if len(slice) == 0 || n <= 0 || n > len(slice) {
+ panic(fmt.Errorf("n is greater than the length of the slice or less than 0, n: %d, length: %d", n, len(slice)))
+ }
+ result = make([]V, 0, n)
+ valid := ConvertSliceToIndexOnlyMap(slice)
+ for i := range valid {
+ result = append(result, slice[i])
+ if len(result) == n {
+ break
+ }
+ }
+ return
+}
+
+// ChooseRandomIndexN 获取切片中的 n 个随机元素的索引
+// - 如果 n 大于切片长度或小于 0 时将会发生 panic
+func ChooseRandomIndexN[S ~[]V, V any](slice S, n int) (result []int) {
+ if len(slice) == 0 {
+ return
+ }
+ if n > len(slice) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the input or less than 0, inputN: %d, length: %d", n, len(slice)))
+ }
+ result = make([]int, n)
+ for i := 0; i < n; i++ {
+ result[i] = random.Int(0, len(slice)-1)
+ }
+ return
+}
+
+// ChooseRandomMapKeyRepeatN 获取 map 中的 n 个随机 key,允许重复
+// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
+func ChooseRandomMapKeyRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (result []K) {
+ if m == nil {
+ return
+ }
+ if n > len(m) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
+ }
+ result = make([]K, n)
+ for i := 0; i < n; i++ {
+ for k := range m {
+ result[i] = k
+ break
+ }
+ }
+ return
+}
+
+// ChooseRandomMapValueRepeatN 获取 map 中的 n 个随机 n,允许重复
+// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
+func ChooseRandomMapValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (result []V) {
+ if m == nil {
+ return
+ }
+ if n > len(m) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
+ }
+ result = make([]V, n)
+ for i := 0; i < n; i++ {
+ for _, v := range m {
+ result[i] = v
+ break
+ }
+ }
+ return
+}
+
+// ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 n 个随机 key 和 v,允许重复
+// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
+func ChooseRandomMapKeyAndValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) M {
+ if m == nil {
+ return nil
+ }
+ if n > len(m) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
+ }
+ result := make(M, n)
+ for i := 0; i < n; i++ {
+ for k, v := range m {
+ result[k] = v
+ break
+ }
+ }
+ return result
+}
+
+// ChooseRandomMapKey 获取 map 中的随机 key
+func ChooseRandomMapKey[M ~map[K]V, K comparable, V any](m M) (k K) {
+ if m == nil {
+ return
+ }
+ for k = range m {
+ return
+ }
+ return
+}
+
+// ChooseRandomMapValue 获取 map 中的随机 value
+func ChooseRandomMapValue[M ~map[K]V, K comparable, V any](m M) (v V) {
+ if m == nil {
+ return
+ }
+ for _, v = range m {
+ return
+ }
+ return
+}
+
+// ChooseRandomMapKeyN 获取 map 中的 inputN 个随机 key
+// - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic
+func ChooseRandomMapKeyN[M ~map[K]V, K comparable, V any](m M, n int) (result []K) {
+ if m == nil {
+ return
+ }
+ if n > len(m) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
+ }
+ result = make([]K, n)
+ i := 0
+ for k := range m {
+ result[i] = k
+ i++
+ if i == n {
+ break
+ }
+ }
+ return
+}
+
+// ChooseRandomMapValueN 获取 map 中的 n 个随机 value
+// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
+func ChooseRandomMapValueN[M ~map[K]V, K comparable, V any](m M, n int) (result []V) {
+ if m == nil {
+ return
+ }
+ if n > len(m) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
+ }
+ result = make([]V, n)
+ i := 0
+ for _, v := range m {
+ result[i] = v
+ i++
+ if i == n {
+ break
+ }
+ }
+ return
+}
+
+// ChooseRandomMapKeyAndValue 获取 map 中的随机 key 和 v
+func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V) {
+ if m == nil {
+ return
+ }
+ for k, v = range m {
+ return
+ }
+ return
+}
+
+// ChooseRandomMapKeyAndValueN 获取 map 中的 inputN 个随机 key 和 v
+// - 如果 n 大于 map 长度或小于 0 时将会发生 panic
+func ChooseRandomMapKeyAndValueN[M ~map[K]V, K comparable, V any](m M, n int) M {
+ if m == nil {
+ return nil
+ }
+ if n > len(m) || n < 0 {
+ panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
+ }
+ result := make(M, n)
+ i := 0
+ for k, v := range m {
+ result[k] = v
+ i++
+ if i == n {
+ break
+ }
+ }
+ return result
+}
diff --git a/toolkit/collection/random_example_test.go b/toolkit/collection/random_example_test.go
new file mode 100644
index 0000000..d19a7d5
--- /dev/null
+++ b/toolkit/collection/random_example_test.go
@@ -0,0 +1,111 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+)
+
+func ExampleChooseRandomSliceElementRepeatN() {
+ result := collection.ChooseRandomSliceElementRepeatN([]int{1}, 10)
+ fmt.Println(result)
+ // Output:
+ // [1 1 1 1 1 1 1 1 1 1]
+}
+
+func ExampleChooseRandomIndexRepeatN() {
+ result := collection.ChooseRandomIndexRepeatN([]int{1}, 10)
+ fmt.Println(result)
+ // Output:
+ // [0 0 0 0 0 0 0 0 0 0]
+}
+
+func ExampleChooseRandomSliceElement() {
+ result := collection.ChooseRandomSliceElement([]int{1})
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleChooseRandomIndex() {
+ result := collection.ChooseRandomIndex([]int{1})
+ fmt.Println(result)
+ // Output:
+ // 0
+}
+
+func ExampleChooseRandomSliceElementN() {
+ result := collection.ChooseRandomSliceElementN([]int{1}, 1)
+ fmt.Println(result)
+ // Output:
+ // [1]
+}
+
+func ExampleChooseRandomIndexN() {
+ result := collection.ChooseRandomIndexN([]int{1}, 1)
+ fmt.Println(result)
+ // Output:
+ // [0]
+}
+
+func ExampleChooseRandomMapKeyRepeatN() {
+ result := collection.ChooseRandomMapKeyRepeatN(map[int]int{1: 1}, 1)
+ fmt.Println(result)
+ // Output:
+ // [1]
+}
+
+func ExampleChooseRandomMapValueRepeatN() {
+ result := collection.ChooseRandomMapValueRepeatN(map[int]int{1: 1}, 1)
+ fmt.Println(result)
+ // Output:
+ // [1]
+}
+
+func ExampleChooseRandomMapKeyAndValueRepeatN() {
+ result := collection.ChooseRandomMapKeyAndValueRepeatN(map[int]int{1: 1}, 1)
+ fmt.Println(result)
+ // Output:
+ // map[1:1]
+}
+
+func ExampleChooseRandomMapKey() {
+ result := collection.ChooseRandomMapKey(map[int]int{1: 1})
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleChooseRandomMapValue() {
+ result := collection.ChooseRandomMapValue(map[int]int{1: 1})
+ fmt.Println(result)
+ // Output:
+ // 1
+}
+
+func ExampleChooseRandomMapKeyN() {
+ result := collection.ChooseRandomMapKeyN(map[int]int{1: 1}, 1)
+ fmt.Println(result)
+ // Output:
+ // [1]
+}
+
+func ExampleChooseRandomMapValueN() {
+ result := collection.ChooseRandomMapValueN(map[int]int{1: 1}, 1)
+ fmt.Println(result)
+ // Output:
+ // [1]
+}
+
+func ExampleChooseRandomMapKeyAndValue() {
+ k, v := collection.ChooseRandomMapKeyAndValue(map[int]int{1: 1})
+ fmt.Println(k, v)
+ // Output:
+ // 1 1
+}
+
+func ExampleChooseRandomMapKeyAndValueN() {
+ result := collection.ChooseRandomMapKeyAndValueN(map[int]int{1: 1}, 1)
+ fmt.Println(result)
+ // Output:
+ // map[1:1]
+}
diff --git a/toolkit/collection/random_test.go b/toolkit/collection/random_test.go
new file mode 100644
index 0000000..27a6e1b
--- /dev/null
+++ b/toolkit/collection/random_test.go
@@ -0,0 +1,280 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "testing"
+)
+
+func TestChooseRandomSliceElementRepeatN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestChooseRandomSliceElementRepeatN_NonEmptySlice", []int{1, 2, 3}, 3},
+ {"TestChooseRandomSliceElementRepeatN_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomSliceElementRepeatN(c.input, 3)
+ if len(result) != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomIndexRepeatN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestChooseRandomIndexRepeatN_NonEmptySlice", []int{1, 2, 3}, 3},
+ {"TestChooseRandomIndexRepeatN_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomIndexRepeatN(c.input, 3)
+ if len(result) != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomSliceElement(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomSliceElement_NonEmptySlice", []int{1, 2, 3}, map[int]bool{1: true, 2: true, 3: true}},
+ {"TestChooseRandomSliceElement_EmptySlice", []int{}, map[int]bool{0: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomSliceElement(c.input)
+ if !c.expected[result] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomIndex(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomIndex_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}},
+ {"TestChooseRandomIndex_EmptySlice", []int{}, map[int]bool{-1: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomIndex(c.input)
+ if !c.expected[result] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomSliceElementN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ }{
+ {"TestChooseRandomSliceElementN_NonEmptySlice", []int{1, 2, 3}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ actual := collection.ChooseRandomSliceElementN(c.input, 3)
+ if !collection.AllInComparableSlice(actual, c.input) {
+ t.Fatalf("%s failed, actual: %v, error: %s", c.name, actual, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomIndexN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomIndexN_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomIndexN(c.input, 3)
+ if !c.expected[result[0]] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapKeyRepeatN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapKeyRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomMapKeyRepeatN(c.input, 3)
+ if !c.expected[result[0]] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapValueRepeatN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomMapValueRepeatN(c.input, 3)
+ if !c.expected[result[0]] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapKeyAndValueRepeatN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapKeyAndValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomMapKeyAndValueRepeatN(c.input, 3)
+ if !c.expected[result[1]] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapKey(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ {"TestChooseRandomMapKey_EmptyMap", map[int]int{}, map[int]bool{0: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomMapKey(c.input)
+ if !c.expected[result] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapValue(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ {"TestChooseRandomMapValue_EmptyMap", map[int]int{}, map[int]bool{0: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomMapValue(c.input)
+ if !c.expected[result] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapValueN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ChooseRandomMapValueN(c.input, 3)
+ if !c.expected[result[0]] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapKeyAndValue(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapKeyAndValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ {"TestChooseRandomMapKeyAndValue_EmptyMap", map[int]int{}, map[int]bool{0: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ k, v := collection.ChooseRandomMapKeyAndValue(c.input)
+ if !c.expected[k] || !c.expected[v] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal")
+ }
+ })
+ }
+}
+
+func TestChooseRandomMapKeyAndValueN(t *testing.T) {
+ var cases = []struct {
+ name string
+ input map[int]int
+ expected map[int]bool
+ }{
+ {"TestChooseRandomMapKeyAndValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ kvm := collection.ChooseRandomMapKeyAndValueN(c.input, 1)
+ for k := range kvm {
+ if !c.expected[k] {
+ t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal")
+ }
+ }
+ })
+ }
+}
diff --git a/toolkit/collection/sort.go b/toolkit/collection/sort.go
new file mode 100644
index 0000000..a8bb183
--- /dev/null
+++ b/toolkit/collection/sort.go
@@ -0,0 +1,60 @@
+package collection
+
+import (
+ "github.com/kercylan98/minotaur/utils/generic"
+ "github.com/kercylan98/minotaur/utils/random"
+ "sort"
+)
+
+// DescBy 返回降序比较结果
+func DescBy[Sort generic.Ordered](a, b Sort) bool {
+ return a > b
+}
+
+// AscBy 返回升序比较结果
+func AscBy[Sort generic.Ordered](a, b Sort) bool {
+ return a < b
+}
+
+// Desc 对切片进行降序排序
+func Desc[S ~[]V, V any, Sort generic.Ordered](slice *S, getter func(index int) Sort) {
+ sort.Slice(*slice, func(i, j int) bool {
+ return getter(i) > getter(j)
+ })
+}
+
+// DescByClone 对切片进行降序排序,返回排序后的切片
+func DescByClone[S ~[]V, V any, Sort generic.Ordered](slice S, getter func(index int) Sort) S {
+ result := CloneSlice(slice)
+ Desc(&result, getter)
+ return result
+
+}
+
+// Asc 对切片进行升序排序
+func Asc[S ~[]V, V any, Sort generic.Ordered](slice *S, getter func(index int) Sort) {
+ sort.Slice(*slice, func(i, j int) bool {
+ return getter(i) < getter(j)
+ })
+}
+
+// AscByClone 对切片进行升序排序,返回排序后的切片
+func AscByClone[S ~[]V, V any, Sort generic.Ordered](slice S, getter func(index int) Sort) S {
+ result := CloneSlice(slice)
+ Asc(&result, getter)
+ return result
+}
+
+// Shuffle 对切片进行随机排序
+func Shuffle[S ~[]V, V any](slice *S) {
+ sort.Slice(*slice, func(i, j int) bool {
+ return random.Int(0, 1) == 0
+ })
+}
+
+// ShuffleByClone 对切片进行随机排序,返回排序后的切片
+func ShuffleByClone[S ~[]V, V any](slice S) S {
+ result := CloneSlice(slice)
+ Shuffle(&result)
+ return result
+}
diff --git a/toolkit/collection/sort_example_test.go b/toolkit/collection/sort_example_test.go
new file mode 100644
index 0000000..be6164d
--- /dev/null
+++ b/toolkit/collection/sort_example_test.go
@@ -0,0 +1,83 @@
+package collection_test
+
+import (
+ "fmt"
+ "github.com/kercylan98/minotaur/utils/collection"
+ "sort"
+)
+
+func ExampleDescBy() {
+ var slice = []int{1, 2, 3}
+ sort.Slice(slice, func(i, j int) bool {
+ return collection.DescBy(slice[i], slice[j])
+ })
+ fmt.Println(slice)
+ // Output:
+ // [3 2 1]
+}
+
+func ExampleAscBy() {
+ var slice = []int{1, 2, 3}
+ sort.Slice(slice, func(i, j int) bool {
+ return collection.AscBy(slice[i], slice[j])
+ })
+ fmt.Println(slice)
+ // Output:
+ // [1 2 3]
+}
+
+func ExampleDesc() {
+ var slice = []int{1, 2, 3}
+ collection.Desc(&slice, func(index int) int {
+ return slice[index]
+ })
+ fmt.Println(slice)
+ // Output:
+ // [3 2 1]
+}
+
+func ExampleDescByClone() {
+ var slice = []int{1, 2, 3}
+ result := collection.DescByClone(slice, func(index int) int {
+ return slice[index]
+ })
+ fmt.Println(result)
+ // Output:
+ // [3 2 1]
+}
+
+func ExampleAsc() {
+ var slice = []int{1, 2, 3}
+ collection.Asc(&slice, func(index int) int {
+ return slice[index]
+ })
+ fmt.Println(slice)
+ // Output:
+ // [1 2 3]
+}
+
+func ExampleAscByClone() {
+ var slice = []int{1, 2, 3}
+ result := collection.AscByClone(slice, func(index int) int {
+ return slice[index]
+ })
+ fmt.Println(result)
+ // Output:
+ // [1 2 3]
+}
+
+func ExampleShuffle() {
+ var slice = []int{1, 2, 3}
+ collection.Shuffle(&slice)
+ fmt.Println(len(slice))
+ // Output:
+ // 3
+}
+
+func ExampleShuffleByClone() {
+ var slice = []int{1, 2, 3}
+ result := collection.ShuffleByClone(slice)
+ fmt.Println(len(result))
+ // Output:
+ // 3
+}
diff --git a/toolkit/collection/sort_test.go b/toolkit/collection/sort_test.go
new file mode 100644
index 0000000..26a0dfc
--- /dev/null
+++ b/toolkit/collection/sort_test.go
@@ -0,0 +1,191 @@
+package collection_test
+
+import (
+ "github.com/kercylan98/minotaur/utils/collection"
+ "sort"
+ "testing"
+)
+
+func TestDescBy(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestDescBy_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
+ {"TestDescBy_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ sort.Slice(c.input, func(i, j int) bool {
+ return collection.DescBy(c.input[i], c.input[j])
+ })
+ for i, v := range c.input {
+ if v != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
+ }
+ }
+ })
+ }
+}
+
+func TestAscBy(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestAscBy_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
+ {"TestAscBy_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ sort.Slice(c.input, func(i, j int) bool {
+ return collection.AscBy(c.input[i], c.input[j])
+ })
+ for i, v := range c.input {
+ if v != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
+ }
+ }
+ })
+ }
+}
+
+func TestDesc(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestDesc_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
+ {"TestDesc_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.Desc(&c.input, func(index int) int {
+ return c.input[index]
+ })
+ for i, v := range c.input {
+ if v != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
+ }
+ }
+ })
+ }
+}
+
+func TestDescByClone(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestDescByClone_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
+ {"TestDescByClone_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.DescByClone(c.input, func(index int) int {
+ return c.input[index]
+ })
+ for i, v := range result {
+ if v != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
+ }
+ }
+ })
+ }
+}
+
+func TestAsc(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestAsc_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
+ {"TestAsc_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.Asc(&c.input, func(index int) int {
+ return c.input[index]
+ })
+ for i, v := range c.input {
+ if v != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
+ }
+ }
+ })
+ }
+}
+
+func TestAscByClone(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected []int
+ }{
+ {"TestAscByClone_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
+ {"TestAscByClone_EmptySlice", []int{}, []int{}},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.AscByClone(c.input, func(index int) int {
+ return c.input[index]
+ })
+ for i, v := range result {
+ if v != c.expected[i] {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
+ }
+ }
+ })
+ }
+}
+
+func TestShuffle(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestShuffle_NonEmptySlice", []int{1, 2, 3}, 3},
+ {"TestShuffle_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ collection.Shuffle(&c.input)
+ if len(c.input) != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
+ }
+ })
+ }
+}
+
+func TestShuffleByClone(t *testing.T) {
+ var cases = []struct {
+ name string
+ input []int
+ expected int
+ }{
+ {"TestShuffleByClone_NonEmptySlice", []int{1, 2, 3}, 3},
+ {"TestShuffleByClone_EmptySlice", []int{}, 0},
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := collection.ShuffleByClone(c.input)
+ if len(result) != c.expected {
+ t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
+ }
+ })
+ }
+}
diff --git a/server/internal/v2/loadbalancer/consistent_hash.go b/toolkit/loadbalancer/consistent_hash.go
similarity index 100%
rename from server/internal/v2/loadbalancer/consistent_hash.go
rename to toolkit/loadbalancer/consistent_hash.go
diff --git a/server/internal/v2/loadbalancer/round_robin.go b/toolkit/loadbalancer/round_robin.go
similarity index 93%
rename from server/internal/v2/loadbalancer/round_robin.go
rename to toolkit/loadbalancer/round_robin.go
index b37b24f..a419f9a 100644
--- a/server/internal/v2/loadbalancer/round_robin.go
+++ b/toolkit/loadbalancer/round_robin.go
@@ -49,7 +49,7 @@ func (r *RoundRobin[Id, T]) Remove(t T) {
prev := r.head
for i := 0; i < r.size; i++ {
- if prev.Next.Value.Id() == t.Id() {
+ if prev.Next.Value.GetId() == t.GetId() {
if prev.Next == r.curr {
r.curr = prev
}
@@ -87,7 +87,7 @@ func (r *RoundRobin[Id, T]) Refresh() {
curr := r.head
for i := 0; i < r.size; i++ {
- if curr.Value.Id() == r.curr.Value.Id() {
+ if curr.Value.GetId() == r.curr.Value.GetId() {
r.curr = curr
return
}
diff --git a/server/internal/v2/loadbalancer/round_robin_item.go b/toolkit/loadbalancer/round_robin_item.go
similarity index 63%
rename from server/internal/v2/loadbalancer/round_robin_item.go
rename to toolkit/loadbalancer/round_robin_item.go
index 50c971d..5c9ed1f 100644
--- a/server/internal/v2/loadbalancer/round_robin_item.go
+++ b/toolkit/loadbalancer/round_robin_item.go
@@ -1,6 +1,6 @@
package loadbalancer
type RoundRobinItem[Id comparable] interface {
- // Id 返回唯一标识
- Id() Id
+ // GetId 返回唯一标识
+ GetId() Id
}
diff --git a/toolkit/message/broker.go b/toolkit/message/broker.go
new file mode 100644
index 0000000..6d67962
--- /dev/null
+++ b/toolkit/message/broker.go
@@ -0,0 +1,8 @@
+package message
+
+// Broker 消息核心的接口定义
+type Broker[I, T comparable] interface {
+ Run()
+ Close()
+ Publish(topic T, event Event[I, T]) error
+}
diff --git a/toolkit/message/brokers/sparse_goroutine.go b/toolkit/message/brokers/sparse_goroutine.go
new file mode 100644
index 0000000..be5a3f0
--- /dev/null
+++ b/toolkit/message/brokers/sparse_goroutine.go
@@ -0,0 +1,147 @@
+package brokers
+
+import (
+ "context"
+ "fmt"
+ "github.com/kercylan98/minotaur/toolkit/loadbalancer"
+ "github.com/kercylan98/minotaur/toolkit/message"
+ "runtime"
+ "sync"
+ "sync/atomic"
+)
+
+const (
+ sparseGoroutineStatusNone = iota - 1 // 未运行
+ sparseGoroutineStatusRunning // 运行中
+ sparseGoroutineStatusClosing // 关闭中
+ sparseGoroutineStatusClosed // 已关闭
+)
+
+type (
+ SparseGoroutineMessageHandler func(handler message.EventExecutor)
+)
+
+func NewSparseGoroutine[I, T comparable](queueFactory func(index int) message.Queue[I, T], handler SparseGoroutineMessageHandler) message.Broker[I, T] {
+ s := &SparseGoroutine[I, T]{
+ lb: loadbalancer.NewRoundRobin[I, message.Queue[I, T]](),
+ queues: make(map[I]message.Queue[I, T]),
+ state: sparseGoroutineStatusNone,
+ location: make(map[T]I),
+ handler: handler,
+ queueFactory: queueFactory,
+ }
+ defaultNum := runtime.NumCPU()
+ s.queueRW.Lock()
+ for i := 0; i < defaultNum; i++ {
+ queue := s.queueFactory(i)
+ s.lb.Add(queue) // 运行前添加到负载均衡器,未运行时允许接收消息
+ queueId := queue.GetId()
+ if _, exist := s.queues[queueId]; exist {
+ panic(fmt.Errorf("duplicate queue id: %v", queueId))
+ }
+ s.queues[queueId] = queue
+ }
+ s.queueRW.Unlock()
+
+ return s
+}
+
+type SparseGoroutine[I, T comparable] struct {
+ state int32 // 状态
+ queueSize int // 队列管道大小
+ queueBufferSize int // 队列缓冲区大小
+ queues map[I]message.Queue[I, T] // 所有使用的队列
+ queueRW sync.RWMutex // 队列读写锁
+ location map[T]I // Topic 所在队列 Id 映射
+ locationRW sync.RWMutex // 所在队列 ID 映射锁
+ lb *loadbalancer.RoundRobin[I, message.Queue[I, T]] // 负载均衡器
+ wg sync.WaitGroup // 等待组
+ handler SparseGoroutineMessageHandler // 消息处理器
+
+ queueFactory func(index int) message.Queue[I, T]
+}
+
+// Run 启动 Reactor,运行队列
+func (s *SparseGoroutine[I, T]) Run() {
+ if !atomic.CompareAndSwapInt32(&s.state, sparseGoroutineStatusNone, sparseGoroutineStatusRunning) {
+ return
+ }
+ s.queueRW.Lock()
+ queues := s.queues
+ for _, queue := range queues {
+ s.wg.Add(1)
+ go queue.Run()
+
+ go func(r *SparseGoroutine[I, T], queue message.Queue[I, T]) {
+ defer r.wg.Done()
+ for h := range queue.Consume() {
+ h.Exec(
+ // onProcess
+ func(topic T, event message.EventExecutor) {
+ s.handler(event)
+ },
+ // onFinish
+ func(topic T, last bool) {
+ if !last {
+ return
+ }
+ s.locationRW.Lock()
+ defer s.locationRW.Unlock()
+ delete(s.location, topic)
+ },
+ )
+ }
+ }(s, queue)
+ }
+ s.queueRW.Unlock()
+ s.wg.Wait()
+}
+
+func (s *SparseGoroutine[I, T]) Close() {
+ if !atomic.CompareAndSwapInt32(&s.state, sparseGoroutineStatusRunning, sparseGoroutineStatusClosing) {
+ return
+ }
+ s.queueRW.Lock()
+ var wg sync.WaitGroup
+ wg.Add(len(s.queues))
+ for _, queue := range s.queues {
+ go func(queue message.Queue[I, T]) {
+ defer wg.Done()
+ queue.Close()
+ }(queue)
+ }
+ wg.Wait()
+ atomic.StoreInt32(&s.state, sparseGoroutineStatusClosed)
+ s.queueRW.Unlock()
+}
+
+// Publish 将消息分发到特定 topic,当 topic 首次使用时,将会根据负载均衡策略选择一个队列
+// - 设置 count 会增加消息的外部计数,当 SparseGoroutine 关闭时会等待外部计数归零
+func (s *SparseGoroutine[I, T]) Publish(topic T, event message.Event[I, T]) error {
+ s.queueRW.RLock()
+ if atomic.LoadInt32(&s.state) > sparseGoroutineStatusClosing {
+ s.queueRW.RUnlock()
+ return fmt.Errorf("broker closing or closed")
+ }
+
+ var next message.Queue[I, T]
+ s.locationRW.RLock()
+ i, exist := s.location[topic]
+ s.locationRW.RUnlock()
+ if !exist {
+ s.locationRW.Lock()
+ if i, exist = s.location[topic]; !exist {
+ next = s.lb.Next()
+ s.location[topic] = next.GetId()
+ } else {
+ next = s.queues[i]
+ }
+ s.locationRW.Unlock()
+ } else {
+ next = s.queues[i]
+ }
+ s.queueRW.RUnlock()
+
+ event.OnInitialize(context.Background(), s)
+ return next.Publish(topic, event)
+}
diff --git a/toolkit/message/event.go b/toolkit/message/event.go
new file mode 100644
index 0000000..4b69df8
--- /dev/null
+++ b/toolkit/message/event.go
@@ -0,0 +1,36 @@
+package message
+
+import (
+ "context"
+ "time"
+)
+
+type Event[I, T comparable] interface {
+ // OnInitialize 消息初始化
+ OnInitialize(ctx context.Context, broker Broker[I, T])
+
+ // OnPublished 消息发布成功
+ OnPublished(topic T, queue Queue[I, T])
+
+ // OnProcess 消息开始处理
+ OnProcess(topic T, queue Queue[I, T], startAt time.Time)
+
+ // OnProcessed 消息处理完成
+ OnProcessed(topic T, queue Queue[I, T], endAt time.Time)
+}
+
+type EventExecutor func()
+type EventHandler[T comparable] func(topic T, event EventExecutor)
+type EventFinisher[I, T comparable] func(topic T, last bool)
+
+type EventInfo[I, T comparable] interface {
+ GetTopic() T
+ Exec(
+ handler EventHandler[T],
+ finisher EventFinisher[I, T],
+ )
+}
+
+func (e EventExecutor) Exec() {
+ e()
+}
diff --git a/toolkit/message/events/asynchronous.go b/toolkit/message/events/asynchronous.go
new file mode 100644
index 0000000..608bb29
--- /dev/null
+++ b/toolkit/message/events/asynchronous.go
@@ -0,0 +1,79 @@
+package events
+
+import (
+ "context"
+ "github.com/kercylan98/minotaur/toolkit/message"
+ "time"
+)
+
+type (
+ AsynchronousActuator func(context.Context, func(context.Context)) // 负责执行异步消息的执行器
+ AsynchronousHandler func(context.Context) error // 异步消息逻辑处理器
+ AsynchronousCallbackHandler func(context.Context, error) // 异步消息回调处理器
+)
+
+// Asynchronous 创建一个异步消息实例,并指定相应的处理器。
+// 该函数接收以下参数:
+// - broker:消息所属的 Broker 实例。
+// - actuator:异步消息的执行器,负责执行异步消息的逻辑,当该参数为空时,将会使用默认的 go func()。
+// - handler:异步消息的逻辑处理器,用于执行实际的异步消息处理逻辑,可选参数。
+// - callback:异步消息的回调处理器,处理消息处理完成后的回调操作,可选参数。
+// - afterHandler:异步消息执行完成后的处理器,用于进行后续的处理操作,可选参数。
+//
+// 该函数除了 handler,其他所有处理器均为同步执行
+//
+// 返回值为一个实现了 Event 接口的异步消息实例。
+func Asynchronous[I, T comparable](
+ actuator AsynchronousActuator,
+ handler AsynchronousHandler,
+ callback AsynchronousCallbackHandler,
+) message.Event[I, T] {
+ m := &asynchronous[I, T]{
+ actuator: actuator,
+ handler: handler,
+ callback: callback,
+ }
+ if m.actuator == nil {
+ m.actuator = func(ctx context.Context, f func(context.Context)) {
+ go f(ctx)
+ }
+ }
+
+ return m
+}
+
+type asynchronous[I, T comparable] struct {
+ ctx context.Context
+ broker message.Broker[I, T]
+ actuator AsynchronousActuator
+ handler AsynchronousHandler
+ callback AsynchronousCallbackHandler
+}
+
+func (s *asynchronous[I, T]) OnInitialize(ctx context.Context, broker message.Broker[I, T]) {
+ s.ctx = ctx
+ s.broker = broker
+}
+
+func (s *asynchronous[I, T]) OnPublished(topic T, queue message.Queue[I, T]) {
+ queue.IncrementCustomMessageCount(topic, 1)
+}
+
+func (s *asynchronous[I, T]) OnProcess(topic T, queue message.Queue[I, T], startAt time.Time) {
+ s.actuator(s.ctx, func(ctx context.Context) {
+ var err error
+ if s.handler != nil {
+ err = s.handler(s.ctx)
+ }
+
+ s.broker.Publish(topic, Synchronous[I, T](func(ctx context.Context) {
+ if s.callback != nil {
+ s.callback(ctx, err)
+ }
+ }))
+ })
+}
+
+func (s *asynchronous[I, T]) OnProcessed(topic T, queue message.Queue[I, T], endAt time.Time) {
+ queue.IncrementCustomMessageCount(topic, -1)
+}
diff --git a/toolkit/message/events/synchronous.go b/toolkit/message/events/synchronous.go
new file mode 100644
index 0000000..9d48e40
--- /dev/null
+++ b/toolkit/message/events/synchronous.go
@@ -0,0 +1,37 @@
+package events
+
+import (
+ "context"
+ "github.com/kercylan98/minotaur/toolkit/message"
+ "time"
+)
+
+type (
+ SynchronousHandler func(context.Context)
+)
+
+func Synchronous[I, T comparable](handler SynchronousHandler) message.Event[I, T] {
+ return &synchronous[I, T]{
+ handler: handler,
+ }
+}
+
+type synchronous[I, T comparable] struct {
+ ctx context.Context
+ handler SynchronousHandler
+}
+
+func (s *synchronous[I, T]) OnInitialize(ctx context.Context, broker message.Broker[I, T]) {
+ s.ctx = ctx
+}
+
+func (s *synchronous[I, T]) OnPublished(topic T, queue message.Queue[I, T]) {
+
+}
+
+func (s *synchronous[I, T]) OnProcess(topic T, queue message.Queue[I, T], startAt time.Time) {
+ s.handler(s.ctx)
+}
+
+func (s *synchronous[I, T]) OnProcessed(topic T, queue message.Queue[I, T], endAt time.Time) {
+}
diff --git a/toolkit/message/producer.go b/toolkit/message/producer.go
new file mode 100644
index 0000000..3396efd
--- /dev/null
+++ b/toolkit/message/producer.go
@@ -0,0 +1,5 @@
+package message
+
+type Producer[T comparable] interface {
+ GetTopic() T
+}
diff --git a/toolkit/message/queue.go b/toolkit/message/queue.go
new file mode 100644
index 0000000..da38f3b
--- /dev/null
+++ b/toolkit/message/queue.go
@@ -0,0 +1,16 @@
+package message
+
+type Queue[I, T comparable] interface {
+ // GetId 获取队列 Id
+ GetId() I
+ // Publish 向队列中推送消息
+ Publish(topic T, event Event[I, T]) error
+ // IncrementCustomMessageCount 增加自定义消息计数
+ IncrementCustomMessageCount(topic T, delta int64)
+ // Run 运行队列
+ Run()
+ // Consume 消费消息
+ Consume() <-chan EventInfo[I, T]
+ // Close 关闭队列
+ Close()
+}
diff --git a/toolkit/message/queues/non_blocking_rw.go b/toolkit/message/queues/non_blocking_rw.go
new file mode 100644
index 0000000..5c9ce5e
--- /dev/null
+++ b/toolkit/message/queues/non_blocking_rw.go
@@ -0,0 +1,197 @@
+package queues
+
+import (
+ "errors"
+ "github.com/kercylan98/minotaur/toolkit/message"
+ "github.com/kercylan98/minotaur/utils/buffer"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+const (
+ NonBlockingRWStatusNone NonBlockingRWState = iota - 1 // 队列未运行
+ NonBlockingRWStatusRunning // 队列运行中
+ NonBlockingRWStatusClosing // 队列关闭中
+ NonBlockingRWStatusClosed // 队列已关闭
+)
+
+var (
+ ErrorQueueClosed = errors.New("queue closed") // 队列已关闭
+ ErrorQueueInvalid = errors.New("queue invalid") // 无效的队列
+)
+
+type NonBlockingRWState = int32
+
+type nonBlockingRWEventInfo[I, T comparable] struct {
+ topic T
+ event message.Event[I, T]
+ exec func(handler message.EventHandler[T], finisher message.EventFinisher[I, T])
+}
+
+func (e *nonBlockingRWEventInfo[I, T]) GetTopic() T {
+ return e.topic
+}
+
+func (e *nonBlockingRWEventInfo[I, T]) Exec(handler message.EventHandler[T], finisher message.EventFinisher[I, T]) {
+ e.exec(handler, finisher)
+}
+
+// NewNonBlockingRW 创建一个并发安全的队列 NonBlockingRW,该队列支持通过 chanSize 自定义读取 channel 的大小,同支持使用 bufferSize 指定 buffer.Ring 的初始大小
+// - closedHandler 可选的设置队列关闭处理函数,在队列关闭后将执行该函数。此刻队列不再可用
+func NewNonBlockingRW[I, T comparable](id I, chanSize, bufferSize int) message.Queue[I, T] {
+ q := &NonBlockingRW[I, T]{
+ id: id,
+ status: NonBlockingRWStatusNone,
+ c: make(chan message.EventInfo[I, T], chanSize),
+ buf: buffer.NewRing[nonBlockingRWEventInfo[I, T]](bufferSize),
+ condRW: &sync.RWMutex{},
+ topics: make(map[T]int64),
+ }
+ q.cond = sync.NewCond(q.condRW)
+ return q
+}
+
+// NonBlockingRW 队列是一个适用于消息处理等场景的并发安全的数据结构
+// - 该队列接收自定义的消息 M,并将消息有序的传入 Read 函数所返回的 channel 中以供处理
+// - 该结构主要实现目标为读写分离且并发安全的非阻塞传输队列,当消费阻塞时以牺牲内存为代价换取消息的生产不阻塞,适用于服务器消息处理等
+// - 该队列保证了消息的完整性,确保消息不丢失,在队列关闭后会等待所有消息处理完毕后进行关闭,并提供 SetClosedHandler 函数来监听队列的关闭信号
+type NonBlockingRW[I, T comparable] struct {
+ id I // 队列 ID
+ status int32 // 状态标志
+ total int64 // 消息总计数
+ topics map[T]int64 // 主题对应的消息计数映射
+ buf *buffer.Ring[nonBlockingRWEventInfo[I, T]] // 消息缓冲区
+ c chan message.EventInfo[I, T] // 消息读取通道
+ cs chan struct{} // 关闭信号
+ cond *sync.Cond // 条件变量
+ condRW *sync.RWMutex // 条件变量的读写锁
+}
+
+// GetId 获取队列 Id
+func (n *NonBlockingRW[I, T]) GetId() I {
+ return n.id
+}
+
+// Run 阻塞的运行队列,当队列非首次运行时,将会引发来自 ErrorQueueInvalid 的 panic
+func (n *NonBlockingRW[I, T]) Run() {
+ if atomic.LoadInt32(&n.status) != NonBlockingRWStatusNone {
+ panic(ErrorQueueInvalid)
+ }
+ atomic.StoreInt32(&n.status, NonBlockingRWStatusRunning)
+ for {
+ n.cond.L.Lock()
+ for n.buf.IsEmpty() {
+ if atomic.LoadInt32(&n.status) >= NonBlockingRWStatusClosing && n.total == 0 {
+ n.cond.L.Unlock()
+ atomic.StoreInt32(&n.status, NonBlockingRWStatusClosed)
+ close(n.c)
+ close(n.cs)
+ return
+ }
+ n.cond.Wait()
+ }
+ items := n.buf.ReadAll()
+ n.cond.L.Unlock()
+ for i := 0; i < len(items); i++ {
+ ei := &items[i]
+ n.c <- ei
+ }
+ }
+}
+
+// Consume 获取队列消息的只读通道,
+func (n *NonBlockingRW[I, T]) Consume() <-chan message.EventInfo[I, T] {
+ return n.c
+}
+
+// Close 关闭队列
+func (n *NonBlockingRW[I, T]) Close() {
+ if atomic.CompareAndSwapInt32(&n.status, NonBlockingRWStatusRunning, NonBlockingRWStatusClosing) {
+ n.cs = make(chan struct{})
+ n.cond.Broadcast()
+ <-n.cs
+ }
+}
+
+// GetMessageCount 获取消息数量
+func (n *NonBlockingRW[I, T]) GetMessageCount() (count int64) {
+ n.condRW.RLock()
+ defer n.condRW.RUnlock()
+ return n.total
+}
+
+// GetTopicMessageCount 获取特定主题的消息数量
+func (n *NonBlockingRW[I, T]) GetTopicMessageCount(topic T) int64 {
+ n.condRW.RLock()
+ defer n.condRW.RUnlock()
+ return n.topics[topic]
+}
+
+func (n *NonBlockingRW[I, T]) Publish(topic T, event message.Event[I, T]) error {
+ if atomic.LoadInt32(&n.status) > NonBlockingRWStatusClosing {
+ return ErrorQueueClosed
+ }
+
+ ei := nonBlockingRWEventInfo[I, T]{
+ topic: topic,
+ event: event,
+ exec: func(handler message.EventHandler[T], finisher message.EventFinisher[I, T]) {
+ defer func() {
+ event.OnProcessed(topic, n, time.Now())
+
+ n.cond.L.Lock()
+ n.total--
+ curr := n.topics[topic] - 1
+ if curr != 0 {
+ n.topics[topic] = curr
+ } else {
+ delete(n.topics, topic)
+ }
+ if finisher != nil {
+ finisher(topic, curr == 0)
+ }
+ n.cond.Signal()
+ n.cond.L.Unlock()
+ }()
+
+ handler(topic, func() {
+ event.OnProcess(topic, n, time.Now())
+ })
+ return
+ },
+ }
+
+ n.cond.L.Lock()
+ n.topics[topic]++
+ n.total++
+ n.buf.Write(ei)
+ //log.Info("消息总计数", log.Int64("计数", q.state.total))
+ n.cond.Signal()
+ n.cond.L.Unlock()
+
+ return nil
+}
+
+func (n *NonBlockingRW[I, T]) IncrementCustomMessageCount(topic T, delta int64) {
+ n.cond.L.Lock()
+ n.total += delta
+ n.topics[topic] += delta
+ n.cond.Signal()
+ n.cond.L.Unlock()
+}
+
+// IsClosed 判断队列是否已关闭
+func (n *NonBlockingRW[I, T]) IsClosed() bool {
+ return atomic.LoadInt32(&n.status) == NonBlockingRWStatusClosed
+}
+
+// IsClosing 判断队列是否正在关闭
+func (n *NonBlockingRW[I, T]) IsClosing() bool {
+ return atomic.LoadInt32(&n.status) == NonBlockingRWStatusClosing
+}
+
+// IsRunning 判断队列是否正在运行
+func (n *NonBlockingRW[I, T]) IsRunning() bool {
+ return atomic.LoadInt32(&n.status) == NonBlockingRWStatusRunning
+}
diff --git a/utils/generator/astgo/README.md b/utils/generator/astgo/README.md
deleted file mode 100644
index 3433548..0000000
--- a/utils/generator/astgo/README.md
+++ /dev/null
@@ -1,197 +0,0 @@
-# Astgo
-
-[](https://pkg.go.dev/github.com/kercylan98/minotaur)
-
-
-暂无介绍...
-
-
-## 目录导航
-列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
-
-展开 / 折叠目录导航
-
-
-> 包级函数定义
-
-|函数名称|描述
-|:--|:--
-|[NewPackage](#NewPackage)|暂无描述...
-
-
-> 类型定义
-
-|类型|名称|描述
-|:--|:--|:--
-|`STRUCT`|[Comment](#struct_Comment)|暂无描述...
-|`STRUCT`|[Field](#struct_Field)|暂无描述...
-|`STRUCT`|[File](#struct_File)|暂无描述...
-|`STRUCT`|[Function](#struct_Function)|暂无描述...
-|`STRUCT`|[Package](#struct_Package)|暂无描述...
-|`STRUCT`|[Struct](#struct_Struct)|暂无描述...
-|`STRUCT`|[Type](#struct_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
deleted file mode 100644
index 9e7527c..0000000
--- a/utils/generator/astgo/comment.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package astgo
-
-import (
- "go/ast"
- "strings"
-)
-
-func newComment(name string, 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 {
- tsc := strings.TrimSpace(cc)
- if strings.HasPrefix(tsc, name) {
- s := strings.TrimSpace(strings.TrimPrefix(tsc, name))
- cc = s
- }
- }
- 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
deleted file mode 100644
index da2a5e2..0000000
--- a/utils/generator/astgo/field.go
+++ /dev/null
@@ -1,37 +0,0 @@
-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(name.String(), 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
deleted file mode 100644
index ff4c03c..0000000
--- a/utils/generator/astgo/file.go
+++ /dev/null
@@ -1,50 +0,0 @@
-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("Package", 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
deleted file mode 100644
index 7613476..0000000
--- a/utils/generator/astgo/function.go
+++ /dev/null
@@ -1,67 +0,0 @@
-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.Name.String(), 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 {
- if f.Test {
- f.decl.Doc = nil
- }
- 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
deleted file mode 100644
index 89b8d73..0000000
--- a/utils/generator/astgo/name.go
+++ /dev/null
@@ -1,45 +0,0 @@
-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:
- str.WriteString(newName(e.Value))
- 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:
- str.WriteString(newName(e.X))
- case *ast.SliceExpr:
- str.WriteString(newName(e.X))
- //case *ast.TypeAssertExpr:
- //case *ast.CallExpr:
- case *ast.StarExpr:
- str.WriteString(newName(e.X))
- case *ast.UnaryExpr:
- str.WriteString(newName(e.X))
- case *ast.BinaryExpr:
- str.WriteString(newName(e.X))
- }
- return str.String()
-}
diff --git a/utils/generator/astgo/package.go b/utils/generator/astgo/package.go
deleted file mode 100644
index 1491e94..0000000
--- a/utils/generator/astgo/package.go
+++ /dev/null
@@ -1,125 +0,0 @@
-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
deleted file mode 100644
index d775db1..0000000
--- a/utils/generator/astgo/package_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-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
deleted file mode 100644
index 5fba9db..0000000
--- a/utils/generator/astgo/struct.go
+++ /dev/null
@@ -1,49 +0,0 @@
-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(astTypeSpec.Name.String(), 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
deleted file mode 100644
index 4e4baf9..0000000
--- a/utils/generator/astgo/type.go
+++ /dev/null
@@ -1,139 +0,0 @@
-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 && strings.HasSuffix(s, ")") && strings.HasPrefix(s, "(") {
- 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
deleted file mode 100644
index cbfe512..0000000
--- a/utils/generator/genreadme/README.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Genreadme
-
-[](https://pkg.go.dev/github.com/kercylan98/minotaur)
-
-
-暂无介绍...
-
-
-## 目录导航
-列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
-
-展开 / 折叠目录导航
-
-
-> 包级函数定义
-
-|函数名称|描述
-|:--|:--
-|[New](#New)|暂无描述...
-
-
-> 类型定义
-
-|类型|名称|描述
-|:--|:--|:--
-|`STRUCT`|[Builder](#struct_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("D:/sources/minotaur", func(path string, info fs.FileInfo, err error) error {
- if !info.IsDir() {
- return nil
- }
- if strings.Contains(strings.TrimPrefix(path, "D:/sources/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
deleted file mode 100644
index a15f662..0000000
--- a/utils/generator/genreadme/builder.go
+++ /dev/null
@@ -1,408 +0,0 @@
-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(`[](https://pkg.go.dev/github.com/kercylan98/minotaur)`))
- b.newLine(fmt.Sprintf(``))
- 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](#struct_%s)", structInfo.Name, 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 %s",
- function.Name,
- func() string {
- var sb strings.Builder
- if len(function.Generic) > 0 {
- sb.WriteString("\\[")
- var genericStr = make([]string, 0)
- for _, field := range function.Generic {
- genericStr = append(genericStr, fmt.Sprintf("%s %s", field.Name, field.Type.Sign))
- }
- sb.WriteString(strings.Join(genericStr, ", "))
- sb.WriteString("\\]")
- }
- return sb.String()
- }(),
- func() string {
- f := funcHandler(function.Params)
- if !strings.HasPrefix(f, "(") {
- f = "(" + f + ")"
- }
- return f
- }(),
- func() string {
- f := strings.TrimSpace(funcHandler(function.Results))
- if len(function.Results) >= 2 && !strings.HasPrefix(f, "(") {
- f = "(" + f + ")"
- }
- return f
- }(),
- )))
- 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("**示例代码:**").newLine()
- if len(example.Comments.Clear) > 0 {
- for _, s := range example.Comments.Clear {
- b.newLine(fmt.Sprintf("%s", s))
- }
- b.newLine().newLine()
- }
- b.newLine("```go\n", example.Code(), "```\n")
- }
- if unitTest := b.p.GetUnitTest(function); unitTest != nil {
- b.detailsStart("查看 / 收起单元测试")
- if len(unitTest.Comments.Clear) > 0 {
- for _, s := range unitTest.Comments.Clear {
- b.newLine(fmt.Sprintf("%s", s))
- }
- b.newLine().newLine()
- }
- b.newLine("```go\n", unitTest.Code(), "```\n")
- b.detailsEnd()
- }
- if benchmarkTest := b.p.GetBenchmarkTest(function); benchmarkTest != nil {
- b.detailsStart("查看 / 收起基准测试")
- if len(benchmarkTest.Comments.Clear) > 0 {
- for _, s := range benchmarkTest.Comments.Clear {
- b.newLine(fmt.Sprintf("%s", s))
- }
- b.newLine().newLine()
- }
- 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.newLine(fmt.Sprintf(``, structInfo.Name))
- 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.newLine(fmt.Sprintf(``, structInfo.Name, function.Name)).newLine()
- 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
- }(),
- func() string {
- f := funcHandler(function.Results)
- if len(function.Results) >= 2 && !strings.HasPrefix(strings.TrimSpace(f), "(") {
- f = "(" + f + ")"
- }
- return f
- }(),
- )))
- b.quote()
- for _, comment := range function.Comments.Clear {
- b.quote(comment)
- }
- b.newLine()
- if example := b.p.GetExampleTest(function); example != nil {
- b.newLine("**示例代码:**").newLine()
- if len(example.Comments.Clear) > 0 {
- for _, s := range example.Comments.Clear {
- b.newLine(fmt.Sprintf("%s", s))
- }
- b.newLine().newLine()
- }
- b.newLine("```go\n", example.Code(), "```\n")
- }
- if unitTest := b.p.GetUnitTest(function); unitTest != nil {
- b.detailsStart("查看 / 收起单元测试")
- if len(unitTest.Comments.Clear) > 0 {
- for _, s := range unitTest.Comments.Clear {
- b.newLine(fmt.Sprintf("%s", s))
- }
- b.newLine().newLine()
- }
- b.newLine("```go\n", unitTest.Code(), "```\n")
- b.detailsEnd()
- }
- if benchmarkTest := b.p.GetBenchmarkTest(function); benchmarkTest != nil {
- b.detailsStart("查看 / 收起基准测试")
- if len(benchmarkTest.Comments.Clear) > 0 {
- for _, s := range benchmarkTest.Comments.Clear {
- b.newLine(fmt.Sprintf("%s", s))
- }
- b.newLine().newLine()
- }
- 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
deleted file mode 100644
index ded811b..0000000
--- a/utils/generator/genreadme/builder_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-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("D:/sources/minotaur", func(path string, info fs.FileInfo, err error) error {
- if !info.IsDir() {
- return nil
- }
- if strings.Contains(strings.TrimPrefix(path, "D:/sources/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
- })
-
-}