other: 服务器消息组件抽离

This commit is contained in:
kercylan98 2024-04-09 16:25:11 +08:00
parent e84a6ee1ae
commit 7ecb13b7c8
106 changed files with 9199 additions and 2153 deletions

View File

@ -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 ""
}

View File

@ -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)
})
})

View File

@ -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
})
})

View File

@ -1,6 +0,0 @@
package message
// Broker 消息核心的接口定义
type Broker[P Producer, Q Queue] interface {
PublishMessage(message Message[P, Q])
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}

View File

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

View File

@ -1,3 +0,0 @@
package message
type Queue comparable

View File

@ -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 的值,将不限制服务器生命周期
// - 该函数支持运行时设置

View File

@ -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)
}

View File

@ -1,10 +0,0 @@
package queue
import (
"errors"
)
var (
ErrorQueueClosed = errors.New("queue closed") // 队列已关闭
ErrorQueueInvalid = errors.New("queue invalid") // 无效的队列
)

View File

@ -1,11 +0,0 @@
package queue
// Message 消息接口定义
type Message[Queue comparable] interface {
// GetQueue 获取消息执行队列
GetQueue() Queue
// OnPublished 消息发布成功
OnPublished(controller Controller)
// OnProcessed 消息处理完成
OnProcessed(controller Controller)
}

View File

@ -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),
)

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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()))
}
}
}

View File

@ -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()
}

View File

@ -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()
})
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
)

350
toolkit/chrono/moment.go Normal file
View File

@ -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)
}

172
toolkit/chrono/period.go Normal file
View File

@ -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)
}

View File

@ -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)
}

218
toolkit/chrono/scheduler.go Normal file
View File

@ -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)
}
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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")
})
}

View File

@ -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
}
}
}

View File

@ -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()))
}

View File

@ -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
}

View File

@ -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
}

View File

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

View File

@ -0,0 +1,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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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]
}
}

View File

@ -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]
}

View File

@ -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)
}
}
})
}
}

View File

@ -0,0 +1,2 @@
// Package collection 定义了各种对于集合操作有用的各种函数
package collection

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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
}
}
}

View File

@ -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
}

View File

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

View File

@ -0,0 +1,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]
}
}
}

View File

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

188
toolkit/collection/loop.go Normal file
View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

25
toolkit/collection/map.go Normal file
View File

@ -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
}

View File

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

View File

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

View File

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

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}
})
}
}

View File

@ -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
}

View File

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

View File

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

View File

@ -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
}

View File

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

View File

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

View File

@ -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
}

View File

@ -1,6 +1,6 @@
package loadbalancer
type RoundRobinItem[Id comparable] interface {
// Id 返回唯一标识
Id() Id
// GetId 返回唯一标识
GetId() Id
}

View File

@ -0,0 +1,8 @@
package message
// Broker 消息核心的接口定义
type Broker[I, T comparable] interface {
Run()
Close()
Publish(topic T, event Event[I, T]) error
}

View File

@ -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)
}

36
toolkit/message/event.go Normal file
View File

@ -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()
}

View File

@ -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)
}

View File

@ -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) {
}

View File

@ -0,0 +1,5 @@
package message
type Producer[T comparable] interface {
GetTopic() T
}

16
toolkit/message/queue.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -1,197 +0,0 @@
# Astgo
[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/kercylan98/minotaur)
![](https://img.shields.io/badge/Email-kercylan@gmail.com-green.svg?style=flat)
暂无介绍...
## 目录导航
列出了该 `package` 下所有的函数及类型定义,可通过目录导航进行快捷跳转 ❤️
<details>
<summary>展开 / 折叠目录导航</summary>
> 包级函数定义
|函数名称|描述
|:--|:--
|[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)|暂无描述...
</details>
***
## 详情信息
#### func NewPackage(dir string) (*Package, error)
<span id="NewPackage"></span>
<details>
<summary>查看 / 收起单元测试</summary>
```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, "", " ")))
}
```
</details>
***
<span id="struct_Comment"></span>
### Comment `STRUCT`
```go
type Comment struct {
Comments []string
Clear []string
}
```
<span id="struct_Field"></span>
### Field `STRUCT`
```go
type Field struct {
Anonymous bool
Name string
Type *Type
Comments *Comment
}
```
<span id="struct_File"></span>
### File `STRUCT`
```go
type File struct {
af *ast.File
owner *Package
FilePath string
Structs []*Struct
Functions []*Function
Comment *Comment
}
```
<span id="struct_File_Package"></span>
#### func (*File) Package() string
***
<span id="struct_Function"></span>
### 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
}
```
<span id="struct_Function_Code"></span>
#### func (*Function) Code() string
***
<span id="struct_Package"></span>
### Package `STRUCT`
```go
type Package struct {
Dir string
Name string
Dirs []string
Files []*File
Functions map[string]*Function
}
```
<span id="struct_Package_StructFunc"></span>
#### func (*Package) StructFunc(name string) []*Function
***
<span id="struct_Package_PackageFunc"></span>
#### func (*Package) PackageFunc() []*Function
***
<span id="struct_Package_Structs"></span>
#### func (*Package) Structs() []*Struct
***
<span id="struct_Package_FileComments"></span>
#### func (*Package) FileComments() *Comment
***
<span id="struct_Package_GetUnitTest"></span>
#### func (*Package) GetUnitTest(f *Function) *Function
***
<span id="struct_Package_GetExampleTest"></span>
#### func (*Package) GetExampleTest(f *Function) *Function
***
<span id="struct_Package_GetBenchmarkTest"></span>
#### func (*Package) GetBenchmarkTest(f *Function) *Function
***
<span id="struct_Struct"></span>
### Struct `STRUCT`
```go
type Struct struct {
Name string
Internal bool
Interface bool
Comments *Comment
Generic []*Field
Fields []*Field
Type *Type
Test bool
}
```
<span id="struct_Type"></span>
### Type `STRUCT`
```go
type Type struct {
expr ast.Expr
Sign string
IsPointer bool
Name string
}
```

View File

@ -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
}

View File

@ -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 // 注释
}

View File

@ -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()
}

View File

@ -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"
}

View File

@ -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()
}

View File

@ -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)]
}

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