other: 服务器消息组件抽离
This commit is contained in:
parent
e84a6ee1ae
commit
7ecb13b7c8
|
@ -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 ""
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package message
|
||||
|
||||
// Broker 消息核心的接口定义
|
||||
type Broker[P Producer, Q Queue] interface {
|
||||
PublishMessage(message Message[P, Q])
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package message
|
||||
|
||||
type Producer interface {
|
||||
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package message
|
||||
|
||||
type Queue comparable
|
|
@ -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 的值,将不限制服务器生命周期
|
||||
// - 该函数支持运行时设置
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorQueueClosed = errors.New("queue closed") // 队列已关闭
|
||||
ErrorQueueInvalid = errors.New("queue invalid") // 无效的队列
|
||||
)
|
|
@ -1,11 +0,0 @@
|
|||
package queue
|
||||
|
||||
// Message 消息接口定义
|
||||
type Message[Queue comparable] interface {
|
||||
// GetQueue 获取消息执行队列
|
||||
GetQueue() Queue
|
||||
// OnPublished 消息发布成功
|
||||
OnPublished(controller Controller)
|
||||
// OnProcessed 消息处理完成
|
||||
OnProcessed(controller Controller)
|
||||
}
|
|
@ -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),
|
||||
)
|
|
@ -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]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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...)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCloneSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestCloneSlice_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
|
||||
{"TestCloneSlice_EmptySlice", []int{}, []int{}},
|
||||
{"TestCloneSlice_NilSlice", nil, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var actual = collection.CloneSlice(c.input)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
if actual[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestCloneMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 1, 2: 2, 3: 3}},
|
||||
{"TestCloneMap_EmptyMap", map[int]int{}, map[int]int{}},
|
||||
{"TestCloneMap_NilMap", nil, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var actual = collection.CloneMap(c.input)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of map is not equal")
|
||||
}
|
||||
for k, v := range actual {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneSliceN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
inputN int
|
||||
expected [][]int
|
||||
}{
|
||||
{"TestCloneSliceN_NonEmptySlice", []int{1, 2, 3}, 2, [][]int{{1, 2, 3}, {1, 2, 3}}},
|
||||
{"TestCloneSliceN_EmptySlice", []int{}, 2, [][]int{{}, {}}},
|
||||
{"TestCloneSliceN_NilSlice", nil, 2, nil},
|
||||
{"TestCloneSliceN_NonEmptySlice_ZeroN", []int{1, 2, 3}, 0, [][]int{}},
|
||||
{"TestCloneSliceN_EmptySlice_ZeroN", []int{}, 0, [][]int{}},
|
||||
{"TestCloneSliceN_NilSlice_ZeroN", nil, 0, nil},
|
||||
{"TestCloneSliceN_NonEmptySlice_NegativeN", []int{1, 2, 3}, -1, [][]int{}},
|
||||
{"TestCloneSliceN_EmptySlice_NegativeN", []int{}, -1, [][]int{}},
|
||||
{"TestCloneSliceN_NilSlice_NegativeN", nil, -1, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var actual = collection.CloneSliceN(c.input, c.inputN)
|
||||
if actual == nil {
|
||||
if c.expected != nil {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, c.inputN, actual, "after clone, the expected is nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
for a, i := range actual {
|
||||
for b, v := range i {
|
||||
if v != c.expected[a][b] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, c.inputN, actual, "after clone, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneMapN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
inputN int
|
||||
expected []map[int]int
|
||||
}{
|
||||
{"TestCloneMapN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 2, []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}},
|
||||
{"TestCloneMapN_EmptyMap", map[int]int{}, 2, []map[int]int{{}, {}}},
|
||||
{"TestCloneMapN_NilMap", nil, 2, nil},
|
||||
{"TestCloneMapN_NonEmptyMap_ZeroN", map[int]int{1: 1, 2: 2, 3: 3}, 0, []map[int]int{}},
|
||||
{"TestCloneMapN_EmptyMap_ZeroN", map[int]int{}, 0, []map[int]int{}},
|
||||
{"TestCloneMapN_NilMap_ZeroN", nil, 0, nil},
|
||||
{"TestCloneMapN_NonEmptyMap_NegativeN", map[int]int{1: 1, 2: 2, 3: 3}, -1, []map[int]int{}},
|
||||
{"TestCloneMapN_EmptyMap_NegativeN", map[int]int{}, -1, []map[int]int{}},
|
||||
{"TestCloneMapN_NilMap_NegativeN", nil, -1, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var actual = collection.CloneMapN(c.input, c.inputN)
|
||||
if actual == nil {
|
||||
if c.expected != nil {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, actual, c.inputN, "after clone, the expected is nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
for a, i := range actual {
|
||||
for b, v := range i {
|
||||
if v != c.expected[a][b] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, actual, c.inputN, "after clone, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneSlices(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input [][]int
|
||||
expected [][]int
|
||||
}{
|
||||
{"TestCloneSlices_NonEmptySlices", [][]int{{1, 2, 3}, {1, 2, 3}}, [][]int{{1, 2, 3}, {1, 2, 3}}},
|
||||
{"TestCloneSlices_EmptySlices", [][]int{{}, {}}, [][]int{{}, {}}},
|
||||
{"TestCloneSlices_NilSlices", nil, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var actual = collection.CloneSlices(c.input...)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
|
||||
}
|
||||
for a, i := range actual {
|
||||
for b, v := range i {
|
||||
if v != c.expected[a][b] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneMaps(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []map[int]int
|
||||
expected []map[int]int
|
||||
}{
|
||||
{"TestCloneMaps_NonEmptyMaps", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}, []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}},
|
||||
{"TestCloneMaps_EmptyMaps", []map[int]int{{}, {}}, []map[int]int{{}, {}}},
|
||||
{"TestCloneMaps_NilMaps", nil, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var actual = collection.CloneMaps(c.input...)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of maps is not equal")
|
||||
}
|
||||
for a, i := range actual {
|
||||
for b, v := range i {
|
||||
if v != c.expected[a][b] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of maps is not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package collection 定义了各种对于集合操作有用的各种函数
|
||||
package collection
|
|
@ -0,0 +1,82 @@
|
|||
package collection
|
||||
|
||||
// ClearSlice 清空切片
|
||||
func ClearSlice[S ~[]V, V any](slice *S) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
*slice = (*slice)[0:0]
|
||||
}
|
||||
|
||||
// ClearMap 清空 map
|
||||
func ClearMap[M ~map[K]V, K comparable, V any](m M) {
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// DropSliceByIndices 删除切片中特定索引的元素
|
||||
func DropSliceByIndices[S ~[]V, V any](slice *S, indices ...int) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
if len(indices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
excludeMap := make(map[int]bool)
|
||||
for _, ex := range indices {
|
||||
excludeMap[ex] = true
|
||||
}
|
||||
|
||||
validElements := (*slice)[:0]
|
||||
for i, v := range *slice {
|
||||
if !excludeMap[i] {
|
||||
validElements = append(validElements, v)
|
||||
}
|
||||
}
|
||||
|
||||
*slice = validElements
|
||||
}
|
||||
|
||||
// DropSliceByCondition 删除切片中符合条件的元素
|
||||
// - condition 的返回值为 true 时,将会删除该元素
|
||||
func DropSliceByCondition[S ~[]V, V any](slice *S, condition func(v V) bool) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
if condition == nil {
|
||||
return
|
||||
}
|
||||
|
||||
validElements := (*slice)[:0]
|
||||
for _, v := range *slice {
|
||||
if !condition(v) {
|
||||
validElements = append(validElements, v)
|
||||
}
|
||||
}
|
||||
|
||||
*slice = validElements
|
||||
}
|
||||
|
||||
// DropSliceOverlappingElements 删除切片中与另一个切片重叠的元素
|
||||
func DropSliceOverlappingElements[S ~[]V, V any](slice *S, anotherSlice []V, comparisonHandler ComparisonHandler[V]) {
|
||||
if slice == nil {
|
||||
return
|
||||
}
|
||||
if anotherSlice == nil {
|
||||
return
|
||||
}
|
||||
if comparisonHandler == nil {
|
||||
return
|
||||
}
|
||||
|
||||
validElements := (*slice)[:0]
|
||||
for _, v := range *slice {
|
||||
if !InSlice(anotherSlice, v, comparisonHandler) {
|
||||
validElements = append(validElements, v)
|
||||
}
|
||||
}
|
||||
|
||||
*slice = validElements
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package collection
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleClearSlice() {
|
||||
slice := []int{1, 2, 3, 4, 5}
|
||||
ClearSlice(&slice)
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// []
|
||||
}
|
||||
|
||||
func ExampleClearMap() {
|
||||
m := map[int]int{1: 1, 2: 2, 3: 3}
|
||||
ClearMap(m)
|
||||
fmt.Println(m)
|
||||
// Output:
|
||||
// map[]
|
||||
}
|
||||
|
||||
func ExampleDropSliceByIndices() {
|
||||
slice := []int{1, 2, 3, 4, 5}
|
||||
DropSliceByIndices(&slice, 1, 3)
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [1 3 5]
|
||||
}
|
||||
|
||||
func ExampleDropSliceByCondition() {
|
||||
slice := []int{1, 2, 3, 4, 5}
|
||||
DropSliceByCondition(&slice, func(v int) bool {
|
||||
return v%2 == 0
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [1 3 5]
|
||||
}
|
||||
|
||||
func ExampleDropSliceOverlappingElements() {
|
||||
slice := []int{1, 2, 3, 4, 5}
|
||||
DropSliceOverlappingElements(&slice, []int{1, 3, 5}, func(source, target int) bool {
|
||||
return source == target
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [2 4]
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClearSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestClearSlice_NonEmptySlice", []int{1, 2, 3}, []int{}},
|
||||
{"TestClearSlice_EmptySlice", []int{}, []int{}},
|
||||
{"TestClearSlice_NilSlice", nil, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.ClearSlice(&c.input)
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(c.input); i++ {
|
||||
if c.input[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestClearMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{}},
|
||||
{"TestClearMap_EmptyMap", map[int]int{}, map[int]int{}},
|
||||
{"TestClearMap_NilMap", nil, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.ClearMap(c.input)
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the length of map is not equal")
|
||||
}
|
||||
for k, v := range c.input {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropSliceByIndices(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
indices []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestDropSliceByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{1, 3}, []int{1, 3, 5}},
|
||||
{"TestDropSliceByIndices_EmptySlice", []int{}, []int{1, 3}, []int{}},
|
||||
{"TestDropSliceByIndices_NilSlice", nil, []int{1, 3}, nil},
|
||||
{"TestDropSliceByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{}, []int{1, 2, 3, 4, 5}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.DropSliceByIndices(&c.input, c.indices...)
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(c.input); i++ {
|
||||
if c.input[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropSliceByCondition(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
condition func(v int) bool
|
||||
expected []int
|
||||
}{
|
||||
{"TestDropSliceByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 0 }, []int{1, 3, 5}},
|
||||
{"TestDropSliceByCondition_EmptySlice", []int{}, func(v int) bool { return v%2 == 0 }, []int{}},
|
||||
{"TestDropSliceByCondition_NilSlice", nil, func(v int) bool { return v%2 == 0 }, nil},
|
||||
{"TestDropSliceByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 1 }, []int{2, 4}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.DropSliceByCondition(&c.input, c.condition)
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(c.input); i++ {
|
||||
if c.input[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropSliceOverlappingElements(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
anotherSlice []int
|
||||
comparisonHandler collection.ComparisonHandler[int]
|
||||
expected []int
|
||||
expectedAnother []int
|
||||
expectedComparison []int
|
||||
}{
|
||||
{"TestDropSliceOverlappingElements_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, []int{1, 2}, []int{6, 7}, []int{3, 4, 5}},
|
||||
{"TestDropSliceOverlappingElements_EmptySlice", []int{}, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, []int{}, []int{3, 4, 5, 6, 7}, []int{}},
|
||||
{"TestDropSliceOverlappingElements_NilSlice", nil, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, nil, []int{3, 4, 5, 6, 7}, nil},
|
||||
{"TestDropSliceOverlappingElements_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{}, func(v1, v2 int) bool { return v1 == v2 }, []int{1, 2, 3, 4, 5}, []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.DropSliceOverlappingElements(&c.input, c.anotherSlice, c.comparisonHandler)
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(c.input); i++ {
|
||||
if c.input[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package collection
|
||||
|
||||
// DeduplicateSliceInPlace 去除切片中的重复元素
|
||||
func DeduplicateSliceInPlace[S ~[]V, V comparable](s *S) {
|
||||
if s == nil || len(*s) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
var m = make(map[V]struct{}, len(*s))
|
||||
var writeIndex int
|
||||
for readIndex, v := range *s {
|
||||
if _, ok := m[v]; !ok {
|
||||
(*s)[writeIndex] = (*s)[readIndex]
|
||||
writeIndex++
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
*s = (*s)[:writeIndex]
|
||||
}
|
||||
|
||||
// DeduplicateSlice 去除切片中的重复元素,返回新切片
|
||||
func DeduplicateSlice[S ~[]V, V comparable](s S) S {
|
||||
if s == nil || len(s) < 2 {
|
||||
return any(s).(S)
|
||||
}
|
||||
|
||||
var r = make([]V, 0, len(s))
|
||||
var m = make(map[V]struct{}, len(s))
|
||||
for _, v := range s {
|
||||
if _, ok := m[v]; !ok {
|
||||
r = append(r, v)
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// DeduplicateSliceInPlaceWithCompare 去除切片中的重复元素,使用自定义的比较函数
|
||||
func DeduplicateSliceInPlaceWithCompare[S ~[]V, V any](s *S, compare func(a, b V) bool) {
|
||||
if s == nil || len(*s) < 2 {
|
||||
return
|
||||
}
|
||||
seen := make(map[int]struct{})
|
||||
resultIndex := 0
|
||||
for i := range *s {
|
||||
unique := true
|
||||
for j := range seen {
|
||||
if compare((*s)[i], (*s)[j]) {
|
||||
unique = false // Found a duplicate
|
||||
break
|
||||
}
|
||||
}
|
||||
if unique {
|
||||
seen[i] = struct{}{}
|
||||
(*s)[resultIndex] = (*s)[i]
|
||||
resultIndex++
|
||||
}
|
||||
}
|
||||
*s = (*s)[:resultIndex]
|
||||
}
|
||||
|
||||
// DeduplicateSliceWithCompare 去除切片中的重复元素,使用自定义的比较函数,返回新的切片
|
||||
func DeduplicateSliceWithCompare[S ~[]V, V any](s S, compare func(a, b V) bool) S {
|
||||
if s == nil || compare == nil || len(s) < 2 {
|
||||
return s
|
||||
}
|
||||
seen := make(map[int]struct{})
|
||||
var result = make([]V, 0, len(s))
|
||||
for i := range s {
|
||||
unique := true
|
||||
for j := range result {
|
||||
if compare(s[i], result[j]) {
|
||||
unique = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if unique {
|
||||
result = append(result, s[i])
|
||||
seen[i] = struct{}{}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package collection
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleDeduplicateSliceInPlace() {
|
||||
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
|
||||
DeduplicateSliceInPlace(&slice)
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [1 2 3 4 5]
|
||||
}
|
||||
|
||||
func ExampleDeduplicateSlice() {
|
||||
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
|
||||
fmt.Println(DeduplicateSlice(slice))
|
||||
// Output:
|
||||
// [1 2 3 4 5]
|
||||
}
|
||||
|
||||
func ExampleDeduplicateSliceInPlaceWithCompare() {
|
||||
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
|
||||
DeduplicateSliceInPlaceWithCompare(&slice, func(a, b int) bool {
|
||||
return a == b
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [1 2 3 4 5]
|
||||
}
|
||||
|
||||
func ExampleDeduplicateSliceWithCompare() {
|
||||
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
|
||||
fmt.Println(DeduplicateSliceWithCompare(slice, func(a, b int) bool {
|
||||
return a == b
|
||||
}))
|
||||
// Output:
|
||||
// [1 2 3 4 5]
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeduplicateSliceInPlace(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{name: "TestDeduplicateSliceInPlace_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
|
||||
{name: "TestDeduplicateSliceInPlace_Empty", input: []int{}, expected: []int{}},
|
||||
{name: "TestDeduplicateSliceInPlace_Nil", input: nil, expected: nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.DeduplicateSliceInPlace(&c.input)
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
|
||||
}
|
||||
for i := 0; i < len(c.input); i++ {
|
||||
av, ev := c.input[i], c.expected[i]
|
||||
if av != ev {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduplicateSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{name: "TestDeduplicateSlice_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
|
||||
{name: "TestDeduplicateSlice_Empty", input: []int{}, expected: []int{}},
|
||||
{name: "TestDeduplicateSlice_Nil", input: nil, expected: nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.DeduplicateSlice(c.input)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, actual)
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
av, ev := actual[i], c.expected[i]
|
||||
if av != ev {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{name: "TestDeduplicateSliceInPlaceWithCompare_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
|
||||
{name: "TestDeduplicateSliceInPlaceWithCompare_Empty", input: []int{}, expected: []int{}},
|
||||
{name: "TestDeduplicateSliceInPlaceWithCompare_Nil", input: nil, expected: nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.DeduplicateSliceInPlaceWithCompare(&c.input, func(a, b int) bool {
|
||||
return a == b
|
||||
})
|
||||
if len(c.input) != len(c.expected) {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
|
||||
}
|
||||
for i := 0; i < len(c.input); i++ {
|
||||
av, ev := c.input[i], c.expected[i]
|
||||
if av != ev {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduplicateSliceWithCompare(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{name: "TestDeduplicateSliceWithCompare_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
|
||||
{name: "TestDeduplicateSliceWithCompare_Empty", input: []int{}, expected: []int{}},
|
||||
{name: "TestDeduplicateSliceWithCompare_Nil", input: nil, expected: nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.DeduplicateSliceWithCompare(c.input, func(a, b int) bool {
|
||||
return a == b
|
||||
})
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, actual)
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
av, ev := actual[i], c.expected[i]
|
||||
if av != ev {
|
||||
t.Errorf("expected: %v, actual: %v", c.expected, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
func ExampleFilterOutByIndices() {
|
||||
var slice = []int{1, 2, 3, 4, 5}
|
||||
var result = collection.FilterOutByIndices(slice, 1, 3)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1 3 5]
|
||||
}
|
||||
|
||||
func ExampleFilterOutByCondition() {
|
||||
var slice = []int{1, 2, 3, 4, 5}
|
||||
var result = collection.FilterOutByCondition(slice, func(v int) bool {
|
||||
return v%2 == 0
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1 3 5]
|
||||
}
|
||||
|
||||
func ExampleFilterOutByKey() {
|
||||
var m = map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
var result = collection.FilterOutByKey(m, "b")
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// map[a:1 c:3]
|
||||
}
|
||||
|
||||
func ExampleFilterOutByValue() {
|
||||
var m = map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
var result = collection.FilterOutByValue(m, 2, func(source, target int) bool {
|
||||
return source == target
|
||||
})
|
||||
fmt.Println(len(result))
|
||||
// Output:
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleFilterOutByKeys() {
|
||||
var m = map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
var result = collection.FilterOutByKeys(m, "a", "c")
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// map[b:2]
|
||||
}
|
||||
|
||||
func ExampleFilterOutByValues() {
|
||||
var m = map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
var result = collection.FilterOutByValues(m, []int{1}, func(source, target int) bool {
|
||||
return source == target
|
||||
})
|
||||
for i, s := range []string{"a", "b", "c"} {
|
||||
fmt.Println(i, result[s])
|
||||
}
|
||||
// Output:
|
||||
// 0 0
|
||||
// 1 2
|
||||
// 2 3
|
||||
}
|
||||
|
||||
func ExampleFilterOutByMap() {
|
||||
var m = map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
var result = collection.FilterOutByMap(m, func(k string, v int) bool {
|
||||
return k == "a" || v == 3
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// map[b:2]
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilterOutByIndices(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
indices []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestFilterOutByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{1, 3}, []int{1, 3, 5}},
|
||||
{"TestFilterOutByIndices_EmptySlice", []int{}, []int{1, 3}, []int{}},
|
||||
{"TestFilterOutByIndices_NilSlice", nil, []int{1, 3}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByIndices(c.input, c.indices...)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
if actual[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOutByCondition(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
condition func(int) bool
|
||||
expected []int
|
||||
}{
|
||||
{"TestFilterOutByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool {
|
||||
return v%2 == 0
|
||||
}, []int{1, 3, 5}},
|
||||
{"TestFilterOutByCondition_EmptySlice", []int{}, func(v int) bool {
|
||||
return v%2 == 0
|
||||
}, []int{}},
|
||||
{"TestFilterOutByCondition_NilSlice", nil, func(v int) bool {
|
||||
return v%2 == 0
|
||||
}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByCondition(c.input, c.condition)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(actual); i++ {
|
||||
if actual[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOutByKey(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
key int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestFilterOutByKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, map[int]int{2: 2, 3: 3}},
|
||||
{"TestFilterOutByKey_EmptyMap", map[int]int{}, 1, map[int]int{}},
|
||||
{"TestFilterOutByKey_NilMap", nil, 1, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByKey(c.input, c.key)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
|
||||
}
|
||||
for k, v := range actual {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOutByValue(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
value int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestFilterOutByValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, map[int]int{2: 2, 3: 3}},
|
||||
{"TestFilterOutByValue_EmptyMap", map[int]int{}, 1, map[int]int{}},
|
||||
{"TestFilterOutByValue_NilMap", nil, 1, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByValue(c.input, c.value, func(source, target int) bool {
|
||||
return source == target
|
||||
})
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
|
||||
}
|
||||
for k, v := range actual {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOutByKeys(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
keys []int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestFilterOutByKeys_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, []int{1, 3}, map[int]int{2: 2}},
|
||||
{"TestFilterOutByKeys_EmptyMap", map[int]int{}, []int{1, 3}, map[int]int{}},
|
||||
{"TestFilterOutByKeys_NilMap", nil, []int{1, 3}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByKeys(c.input, c.keys...)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
|
||||
}
|
||||
for k, v := range actual {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOutByValues(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
values []int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestFilterOutByValues_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, []int{1, 3}, map[int]int{2: 2}},
|
||||
{"TestFilterOutByValues_EmptyMap", map[int]int{}, []int{1, 3}, map[int]int{}},
|
||||
{"TestFilterOutByValues_NilMap", nil, []int{1, 3}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByValues(c.input, c.values, func(source, target int) bool {
|
||||
return source == target
|
||||
})
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
|
||||
}
|
||||
for k, v := range actual {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterOutByMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
filter map[int]int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestFilterOutByMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 1, 3: 3}, map[int]int{2: 2}},
|
||||
{"TestFilterOutByMap_EmptyMap", map[int]int{}, map[int]int{1: 1, 3: 3}, map[int]int{}},
|
||||
{"TestFilterOutByMap_NilMap", nil, map[int]int{1: 1, 3: 3}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FilterOutByMap(c.input, func(k int, v int) bool {
|
||||
return c.filter[k] == v
|
||||
})
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
|
||||
}
|
||||
for k, v := range actual {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
package collection
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
)
|
||||
|
||||
// FindLoopedNextInSlice 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始
|
||||
// - 当 i 为负数时将返回第一个元素
|
||||
func FindLoopedNextInSlice[S ~[]V, V any](slice S, i int) (next int, value V) {
|
||||
if i < 0 {
|
||||
return 0, slice[0]
|
||||
}
|
||||
next = i + 1
|
||||
if next == len(slice) {
|
||||
next = 0
|
||||
}
|
||||
return next, slice[next]
|
||||
}
|
||||
|
||||
// FindLoopedPrevInSlice 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始
|
||||
// - 当 i 为负数时将返回最后一个元素
|
||||
func FindLoopedPrevInSlice[S ~[]V, V any](slice S, i int) (prev int, value V) {
|
||||
if i < 0 {
|
||||
return len(slice) - 1, slice[len(slice)-1]
|
||||
}
|
||||
prev = i - 1
|
||||
if prev == -1 {
|
||||
prev = len(slice) - 1
|
||||
}
|
||||
return prev, slice[prev]
|
||||
}
|
||||
|
||||
// FindCombinationsInSliceByRange 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内
|
||||
func FindCombinationsInSliceByRange[S ~[]V, V any](s S, minSize, maxSize int) []S {
|
||||
n := len(s)
|
||||
if n == 0 || minSize <= 0 || maxSize <= 0 || minSize > maxSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []S
|
||||
var currentCombination S
|
||||
|
||||
var backtrack func(startIndex int, currentSize int)
|
||||
backtrack = func(startIndex int, currentSize int) {
|
||||
if currentSize >= minSize && currentSize <= maxSize {
|
||||
combination := make(S, len(currentCombination))
|
||||
copy(combination, currentCombination)
|
||||
result = append(result, combination)
|
||||
}
|
||||
|
||||
for i := startIndex; i < n; i++ {
|
||||
currentCombination = append(currentCombination, s[i])
|
||||
backtrack(i+1, currentSize+1)
|
||||
currentCombination = currentCombination[:len(currentCombination)-1]
|
||||
}
|
||||
}
|
||||
|
||||
backtrack(0, 0)
|
||||
return result
|
||||
}
|
||||
|
||||
// FindFirstOrDefaultInSlice 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值
|
||||
func FindFirstOrDefaultInSlice[S ~[]V, V any](slice S, defaultValue V) V {
|
||||
if len(slice) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
return slice[0]
|
||||
}
|
||||
|
||||
// FindOrDefaultInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值
|
||||
func FindOrDefaultInSlice[S ~[]V, V any](slice S, defaultValue V, handler func(v V) bool) (t V) {
|
||||
if len(slice) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
for _, v := range slice {
|
||||
if handler(v) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// FindOrDefaultInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值
|
||||
func FindOrDefaultInComparableSlice[S ~[]V, V comparable](slice S, v V, defaultValue V) (t V) {
|
||||
if len(slice) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
for _, value := range slice {
|
||||
if value == v {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// FindInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1
|
||||
func FindInSlice[S ~[]V, V any](slice S, handler func(v V) bool) (i int, t V) {
|
||||
if len(slice) == 0 {
|
||||
return -1, t
|
||||
}
|
||||
for i, v := range slice {
|
||||
if handler(v) {
|
||||
return i, v
|
||||
}
|
||||
}
|
||||
return -1, t
|
||||
}
|
||||
|
||||
// FindIndexInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1
|
||||
func FindIndexInSlice[S ~[]V, V any](slice S, handler func(v V) bool) int {
|
||||
if len(slice) == 0 {
|
||||
return -1
|
||||
}
|
||||
for i, v := range slice {
|
||||
if handler(v) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// FindInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1
|
||||
func FindInComparableSlice[S ~[]V, V comparable](slice S, v V) (i int, t V) {
|
||||
if len(slice) == 0 {
|
||||
return -1, t
|
||||
}
|
||||
for i, value := range slice {
|
||||
if value == v {
|
||||
return i, value
|
||||
}
|
||||
}
|
||||
return -1, t
|
||||
}
|
||||
|
||||
// FindIndexInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1
|
||||
func FindIndexInComparableSlice[S ~[]V, V comparable](slice S, v V) int {
|
||||
if len(slice) == 0 {
|
||||
return -1
|
||||
}
|
||||
for i, value := range slice {
|
||||
if value == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// FindMinimumInComparableSlice 获取切片中的最小值
|
||||
func FindMinimumInComparableSlice[S ~[]V, V generic.Ordered](slice S) (result V) {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
result = slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if result > slice[i] {
|
||||
result = slice[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMinimumInSlice 获取切片中的最小值
|
||||
func FindMinimumInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (result V) {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
result = slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if handler(result) > handler(slice[i]) {
|
||||
result = slice[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMaximumInComparableSlice 获取切片中的最大值
|
||||
func FindMaximumInComparableSlice[S ~[]V, V generic.Ordered](slice S) (result V) {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
result = slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if result < slice[i] {
|
||||
result = slice[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMaximumInSlice 获取切片中的最大值
|
||||
func FindMaximumInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (result V) {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
result = slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if handler(result) < handler(slice[i]) {
|
||||
result = slice[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMin2MaxInComparableSlice 获取切片中的最小值和最大值
|
||||
func FindMin2MaxInComparableSlice[S ~[]V, V generic.Ordered](slice S) (min, max V) {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
min = slice[0]
|
||||
max = slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if min > slice[i] {
|
||||
min = slice[i]
|
||||
}
|
||||
if max < slice[i] {
|
||||
max = slice[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMin2MaxInSlice 获取切片中的最小值和最大值
|
||||
func FindMin2MaxInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (min, max V) {
|
||||
if len(slice) == 0 {
|
||||
return
|
||||
}
|
||||
min = slice[0]
|
||||
max = slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if handler(min) > handler(slice[i]) {
|
||||
min = slice[i]
|
||||
}
|
||||
if handler(max) < handler(slice[i]) {
|
||||
max = slice[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMinFromComparableMap 获取 map 中的最小值
|
||||
func FindMinFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (result V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var first bool
|
||||
for _, v := range m {
|
||||
if !first {
|
||||
result = v
|
||||
first = true
|
||||
continue
|
||||
}
|
||||
if result > v {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMinFromMap 获取 map 中的最小值
|
||||
func FindMinFromMap[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, handler OrderedValueGetter[V, N]) (result V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var first bool
|
||||
for _, v := range m {
|
||||
if !first {
|
||||
result = v
|
||||
first = true
|
||||
continue
|
||||
}
|
||||
if handler(result) > handler(v) {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMaxFromComparableMap 获取 map 中的最大值
|
||||
func FindMaxFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (result V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range m {
|
||||
if result < v {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMaxFromMap 获取 map 中的最大值
|
||||
func FindMaxFromMap[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, handler OrderedValueGetter[V, N]) (result V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range m {
|
||||
if handler(result) < handler(v) {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMin2MaxFromComparableMap 获取 map 中的最小值和最大值
|
||||
func FindMin2MaxFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (min, max V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var first bool
|
||||
for _, v := range m {
|
||||
if !first {
|
||||
min = v
|
||||
max = v
|
||||
first = true
|
||||
continue
|
||||
}
|
||||
if min > v {
|
||||
min = v
|
||||
}
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindMin2MaxFromMap 获取 map 中的最小值和最大值
|
||||
func FindMin2MaxFromMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (min, max V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
var first bool
|
||||
for _, v := range m {
|
||||
if !first {
|
||||
min = v
|
||||
max = v
|
||||
first = true
|
||||
continue
|
||||
}
|
||||
if min > v {
|
||||
min = v
|
||||
}
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
func ExampleFindLoopedNextInSlice() {
|
||||
next, v := collection.FindLoopedNextInSlice([]int{1, 2, 3}, 1)
|
||||
fmt.Println(next, v)
|
||||
// Output:
|
||||
// 2 3
|
||||
}
|
||||
|
||||
func ExampleFindLoopedPrevInSlice() {
|
||||
prev, v := collection.FindLoopedPrevInSlice([]int{1, 2, 3}, 1)
|
||||
fmt.Println(prev, v)
|
||||
// Output:
|
||||
// 0 1
|
||||
}
|
||||
|
||||
func ExampleFindCombinationsInSliceByRange() {
|
||||
result := collection.FindCombinationsInSliceByRange([]int{1, 2, 3}, 1, 2)
|
||||
fmt.Println(len(result))
|
||||
// Output:
|
||||
// 6
|
||||
}
|
||||
|
||||
func ExampleFindFirstOrDefaultInSlice() {
|
||||
result := collection.FindFirstOrDefaultInSlice([]int{1, 2, 3}, 0)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindOrDefaultInSlice() {
|
||||
result := collection.FindOrDefaultInSlice([]int{1, 2, 3}, 0, func(v int) bool {
|
||||
return v == 2
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleFindOrDefaultInComparableSlice() {
|
||||
result := collection.FindOrDefaultInComparableSlice([]int{1, 2, 3}, 2, 0)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleFindInSlice() {
|
||||
_, result := collection.FindInSlice([]int{1, 2, 3}, func(v int) bool {
|
||||
return v == 2
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleFindIndexInSlice() {
|
||||
result := collection.FindIndexInSlice([]int{1, 2, 3}, func(v int) bool {
|
||||
return v == 2
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindInComparableSlice() {
|
||||
index, result := collection.FindInComparableSlice([]int{1, 2, 3}, 2)
|
||||
fmt.Println(index, result)
|
||||
// Output:
|
||||
// 1 2
|
||||
}
|
||||
|
||||
func ExampleFindIndexInComparableSlice() {
|
||||
result := collection.FindIndexInComparableSlice([]int{1, 2, 3}, 2)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindMinimumInComparableSlice() {
|
||||
result := collection.FindMinimumInComparableSlice([]int{1, 2, 3})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindMinimumInSlice() {
|
||||
result := collection.FindMinimumInSlice([]int{1, 2, 3}, func(v int) int {
|
||||
return v
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindMaximumInComparableSlice() {
|
||||
result := collection.FindMaximumInComparableSlice([]int{1, 2, 3})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleFindMaximumInSlice() {
|
||||
result := collection.FindMaximumInSlice([]int{1, 2, 3}, func(v int) int {
|
||||
return v
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleFindMin2MaxInComparableSlice() {
|
||||
minimum, maximum := collection.FindMin2MaxInComparableSlice([]int{1, 2, 3})
|
||||
fmt.Println(minimum, maximum)
|
||||
// Output:
|
||||
// 1 3
|
||||
}
|
||||
|
||||
func ExampleFindMin2MaxInSlice() {
|
||||
minimum, maximum := collection.FindMin2MaxInSlice([]int{1, 2, 3}, func(v int) int {
|
||||
return v
|
||||
})
|
||||
fmt.Println(minimum, maximum)
|
||||
// Output:
|
||||
// 1 3
|
||||
}
|
||||
|
||||
func ExampleFindMinFromComparableMap() {
|
||||
result := collection.FindMinFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindMinFromMap() {
|
||||
result := collection.FindMinFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int {
|
||||
return v
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleFindMaxFromComparableMap() {
|
||||
result := collection.FindMaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleFindMaxFromMap() {
|
||||
result := collection.FindMaxFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int {
|
||||
return v
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleFindMin2MaxFromComparableMap() {
|
||||
minimum, maximum := collection.FindMin2MaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
|
||||
fmt.Println(minimum, maximum)
|
||||
// Output:
|
||||
// 1 3
|
||||
}
|
||||
|
||||
func ExampleFindMin2MaxFromMap() {
|
||||
minimum, maximum := collection.FindMin2MaxFromMap(map[int]int{1: 1, 2: 2, 3: 3})
|
||||
fmt.Println(minimum, maximum)
|
||||
// Output:
|
||||
// 1 3
|
||||
}
|
|
@ -0,0 +1,467 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFindLoopedNextInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
i int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindLoopedNextInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 2},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual, _ := collection.FindLoopedNextInSlice(c.input, c.i)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the next index of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindLoopedPrevInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
i int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindLoopedPrevInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual, _ := collection.FindLoopedPrevInSlice(c.input, c.i)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the prev index of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCombinationsInSliceByRange(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
minSize int
|
||||
maxSize int
|
||||
expected [][]int
|
||||
}{
|
||||
{"TestFindCombinationsInSliceByRange_NonEmptySlice", []int{1, 2, 3}, 1, 2, [][]int{{1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindCombinationsInSliceByRange(c.input, c.minSize, c.maxSize)
|
||||
if len(actual) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindFirstOrDefaultInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindFirstOrDefaultInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
|
||||
{"TestFindFirstOrDefaultInSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindFirstOrDefaultInSlice(c.input, 0)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the first element of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOrDefaultInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindOrDefaultInSlice_NonEmptySlice", []int{1, 2, 3}, 2},
|
||||
{"TestFindOrDefaultInSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindOrDefaultInSlice(c.input, 0, func(v int) bool {
|
||||
return v == 2
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOrDefaultInComparableSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindOrDefaultInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2},
|
||||
{"TestFindOrDefaultInComparableSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindOrDefaultInComparableSlice(c.input, 2, 0)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindInSlice_NonEmptySlice", []int{1, 2, 3}, 2},
|
||||
{"TestFindInSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
_, actual := collection.FindInSlice(c.input, func(v int) bool {
|
||||
return v == 2
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindIndexInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindIndexInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
|
||||
{"TestFindIndexInSlice_EmptySlice", []int{}, -1},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindIndexInSlice(c.input, func(v int) bool {
|
||||
return v == 2
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindInComparableSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2},
|
||||
{"TestFindInComparableSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
_, actual := collection.FindInComparableSlice(c.input, 2)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindIndexInComparableSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindIndexInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1},
|
||||
{"TestFindIndexInComparableSlice_EmptySlice", []int{}, -1},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindIndexInComparableSlice(c.input, 2)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMinimumInComparableSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMinimumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1},
|
||||
{"TestFindMinimumInComparableSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMinimumInComparableSlice(c.input)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMinimumInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMinimumInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
|
||||
{"TestFindMinimumInSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMinimumInSlice(c.input, func(v int) int {
|
||||
return v
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMaximumInComparableSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMaximumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 3},
|
||||
{"TestFindMaximumInComparableSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMaximumInComparableSlice(c.input)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMaximumInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMaximumInSlice_NonEmptySlice", []int{1, 2, 3}, 3},
|
||||
{"TestFindMaximumInSlice_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMaximumInSlice(c.input, func(v int) int {
|
||||
return v
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMin2MaxInComparableSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expectedMin int
|
||||
expectedMax int
|
||||
}{
|
||||
{"TestFindMin2MaxInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3},
|
||||
{"TestFindMin2MaxInComparableSlice_EmptySlice", []int{}, 0, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
minimum, maximum := collection.FindMin2MaxInComparableSlice(c.input)
|
||||
if minimum != c.expectedMin || maximum != c.expectedMax {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMin2MaxInSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expectedMin int
|
||||
expectedMax int
|
||||
}{
|
||||
{"TestFindMin2MaxInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3},
|
||||
{"TestFindMin2MaxInSlice_EmptySlice", []int{}, 0, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
minimum, maximum := collection.FindMin2MaxInSlice(c.input, func(v int) int {
|
||||
return v
|
||||
})
|
||||
if minimum != c.expectedMin || maximum != c.expectedMax {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMinFromComparableMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMinFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1},
|
||||
{"TestFindMinFromComparableMap_EmptyMap", map[int]int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMinFromComparableMap(c.input)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMinFromMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMinFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1},
|
||||
{"TestFindMinFromMap_EmptyMap", map[int]int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMinFromMap(c.input, func(v int) int {
|
||||
return v
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMaxFromComparableMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3},
|
||||
{"TestFindMaxFromComparableMap_EmptyMap", map[int]int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMaxFromComparableMap(c.input)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMaxFromMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected int
|
||||
}{
|
||||
{"TestFindMaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3},
|
||||
{"TestFindMaxFromMap_EmptyMap", map[int]int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.FindMaxFromMap(c.input, func(v int) int {
|
||||
return v
|
||||
})
|
||||
if actual != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMin2MaxFromComparableMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expectedMin int
|
||||
expectedMax int
|
||||
}{
|
||||
{"TestFindMin2MaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3},
|
||||
{"TestFindMin2MaxFromComparableMap_EmptyMap", map[int]int{}, 0, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
minimum, maximum := collection.FindMin2MaxFromComparableMap(c.input)
|
||||
if minimum != c.expectedMin || maximum != c.expectedMax {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMin2MaxFromMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expectedMin int
|
||||
expectedMax int
|
||||
}{
|
||||
{"TestFindMin2MaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3},
|
||||
{"TestFindMin2MaxFromMap_EmptyMap", map[int]int{}, 0, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
minimum, maximum := collection.FindMin2MaxFromMap(c.input)
|
||||
if minimum != c.expectedMin || maximum != c.expectedMax {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package collection
|
||||
|
||||
// SwapSlice 将切片中的两个元素进行交换
|
||||
func SwapSlice[S ~[]V, V any](slice *S, i, j int) {
|
||||
if i < 0 || j < 0 || i >= len(*slice) || j >= len(*slice) {
|
||||
return
|
||||
}
|
||||
(*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
func ExampleSwapSlice() {
|
||||
var s = []int{1, 2, 3}
|
||||
collection.SwapSlice(&s, 0, 1)
|
||||
fmt.Println(s)
|
||||
// Output:
|
||||
// [2 1 3]
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSwapSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
slice []int
|
||||
i int
|
||||
j int
|
||||
expect []int
|
||||
}{
|
||||
{"TestSwapSliceNonEmpty", []int{1, 2, 3}, 0, 1, []int{2, 1, 3}},
|
||||
{"TestSwapSliceEmpty", []int{}, 0, 0, []int{}},
|
||||
{"TestSwapSliceIndexOutOfBound", []int{1, 2, 3}, 0, 3, []int{1, 2, 3}},
|
||||
{"TestSwapSliceNil", nil, 0, 0, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.SwapSlice(&c.slice, c.i, c.j)
|
||||
for i, v := range c.slice {
|
||||
if v != c.expect[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expect, c.slice, "the slice is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package listings_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection/listings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrioritySlice_Append(t *testing.T) {
|
||||
var s = listings.NewPrioritySlice[string]()
|
||||
s.Append("name_1", 2)
|
||||
s.Append("name_2", 1)
|
||||
fmt.Println(s)
|
||||
}
|
|
@ -0,0 +1,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]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
func ExampleMappingFromSlice() {
|
||||
result := collection.MappingFromSlice([]int{1, 2, 3}, func(value int) int {
|
||||
return value + 1
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [2 3 4]
|
||||
}
|
||||
|
||||
func ExampleMappingFromMap() {
|
||||
result := collection.MappingFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(value int) int {
|
||||
return value + 1
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// map[1:2 2:3 3:4]
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMappingFromSlice(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestMappingFromSlice_NonEmptySlice", []int{1, 2, 3}, []int{2, 3, 4}},
|
||||
{"TestMappingFromSlice_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.MappingFromSlice[[]int, []int](c.input, func(value int) int {
|
||||
return value + 1
|
||||
})
|
||||
if len(result) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
for i := 0; i < len(result); i++ {
|
||||
if result[i] != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMappingFromMap(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]int
|
||||
}{
|
||||
{"TestMappingFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 2, 2: 3, 3: 4}},
|
||||
{"TestMappingFromMap_EmptyMap", map[int]int{}, map[int]int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.MappingFromMap[map[int]int, map[int]int](c.input, func(value int) int {
|
||||
return value + 1
|
||||
})
|
||||
if len(result) != len(c.expected) {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
for k, v := range result {
|
||||
if v != c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package mappings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewSyncMap 创建一个 SyncMap
|
||||
func NewSyncMap[K comparable, V any](source ...map[K]V) *SyncMap[K, V] {
|
||||
m := &SyncMap[K, V]{
|
||||
data: make(map[K]V),
|
||||
}
|
||||
if len(source) > 0 {
|
||||
m.data = collection.MergeMaps(source...)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// SyncMap 是基于 sync.RWMutex 实现的线程安全的 map
|
||||
// - 适用于要考虑并发读写但是并发读写的频率不高的情况
|
||||
type SyncMap[K comparable, V any] struct {
|
||||
lock sync.RWMutex
|
||||
data map[K]V
|
||||
atom bool
|
||||
}
|
||||
|
||||
// Set 设置一个值
|
||||
func (sm *SyncMap[K, V]) Set(key K, value V) {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
sm.data[key] = value
|
||||
}
|
||||
|
||||
// Get 获取一个值
|
||||
func (sm *SyncMap[K, V]) Get(key K) V {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
return sm.data[key]
|
||||
}
|
||||
|
||||
// Atom 原子操作
|
||||
func (sm *SyncMap[K, V]) Atom(handle func(m map[K]V)) {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
handle(sm.data)
|
||||
}
|
||||
|
||||
// Exist 判断是否存在
|
||||
func (sm *SyncMap[K, V]) Exist(key K) bool {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
_, exist := sm.data[key]
|
||||
return exist
|
||||
}
|
||||
|
||||
// GetExist 获取一个值并判断是否存在
|
||||
func (sm *SyncMap[K, V]) GetExist(key K) (V, bool) {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
value, exist := sm.data[key]
|
||||
return value, exist
|
||||
}
|
||||
|
||||
// Delete 删除一个值
|
||||
func (sm *SyncMap[K, V]) Delete(key K) {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
delete(sm.data, key)
|
||||
}
|
||||
|
||||
// DeleteGet 删除一个值并返回
|
||||
func (sm *SyncMap[K, V]) DeleteGet(key K) V {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
v := sm.data[key]
|
||||
delete(sm.data, key)
|
||||
return v
|
||||
}
|
||||
|
||||
// DeleteGetExist 删除一个值并返回是否存在
|
||||
func (sm *SyncMap[K, V]) DeleteGetExist(key K) (V, bool) {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
v, exist := sm.data[key]
|
||||
delete(sm.data, key)
|
||||
return v, exist
|
||||
}
|
||||
|
||||
// DeleteExist 删除一个值并返回是否存在
|
||||
func (sm *SyncMap[K, V]) DeleteExist(key K) bool {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
if _, exist := sm.data[key]; !exist {
|
||||
sm.lock.Unlock()
|
||||
return exist
|
||||
}
|
||||
delete(sm.data, key)
|
||||
return true
|
||||
}
|
||||
|
||||
// Clear 清空
|
||||
func (sm *SyncMap[K, V]) Clear() {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
for k := range sm.data {
|
||||
delete(sm.data, k)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearHandle 清空并处理
|
||||
func (sm *SyncMap[K, V]) ClearHandle(handle func(key K, value V)) {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
for k, v := range sm.data {
|
||||
handle(k, v)
|
||||
delete(sm.data, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Range 遍历所有值,如果 handle 返回 true 则停止遍历
|
||||
func (sm *SyncMap[K, V]) Range(handle func(key K, value V) bool) {
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
defer sm.lock.Unlock()
|
||||
}
|
||||
for k, v := range sm.data {
|
||||
key, value := k, v
|
||||
if handle(key, value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keys 获取所有的键
|
||||
func (sm *SyncMap[K, V]) Keys() []K {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
var s = make([]K, 0, len(sm.data))
|
||||
for k := range sm.data {
|
||||
s = append(s, k)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Slice 获取所有的值
|
||||
func (sm *SyncMap[K, V]) Slice() []V {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
var s = make([]V, 0, len(sm.data))
|
||||
for _, v := range sm.data {
|
||||
s = append(s, v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Map 转换为普通 map
|
||||
func (sm *SyncMap[K, V]) Map() map[K]V {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
var m = make(map[K]V)
|
||||
for k, v := range sm.data {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Size 获取数量
|
||||
func (sm *SyncMap[K, V]) Size() int {
|
||||
if !sm.atom {
|
||||
sm.lock.RLock()
|
||||
defer sm.lock.RUnlock()
|
||||
}
|
||||
return len(sm.data)
|
||||
}
|
||||
|
||||
func (sm *SyncMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
m := sm.Map()
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (sm *SyncMap[K, V]) UnmarshalJSON(bytes []byte) error {
|
||||
var m = make(map[K]V)
|
||||
if !sm.atom {
|
||||
sm.lock.Lock()
|
||||
sm.lock.Unlock()
|
||||
}
|
||||
if err := json.Unmarshal(bytes, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
sm.data = m
|
||||
return nil
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
)
|
||||
|
||||
func ExampleChooseRandomSliceElementRepeatN() {
|
||||
result := collection.ChooseRandomSliceElementRepeatN([]int{1}, 10)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1 1 1 1 1 1 1 1 1 1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomIndexRepeatN() {
|
||||
result := collection.ChooseRandomIndexRepeatN([]int{1}, 10)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [0 0 0 0 0 0 0 0 0 0]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomSliceElement() {
|
||||
result := collection.ChooseRandomSliceElement([]int{1})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleChooseRandomIndex() {
|
||||
result := collection.ChooseRandomIndex([]int{1})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleChooseRandomSliceElementN() {
|
||||
result := collection.ChooseRandomSliceElementN([]int{1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomIndexN() {
|
||||
result := collection.ChooseRandomIndexN([]int{1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [0]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapKeyRepeatN() {
|
||||
result := collection.ChooseRandomMapKeyRepeatN(map[int]int{1: 1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapValueRepeatN() {
|
||||
result := collection.ChooseRandomMapValueRepeatN(map[int]int{1: 1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapKeyAndValueRepeatN() {
|
||||
result := collection.ChooseRandomMapKeyAndValueRepeatN(map[int]int{1: 1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// map[1:1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapKey() {
|
||||
result := collection.ChooseRandomMapKey(map[int]int{1: 1})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapValue() {
|
||||
result := collection.ChooseRandomMapValue(map[int]int{1: 1})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapKeyN() {
|
||||
result := collection.ChooseRandomMapKeyN(map[int]int{1: 1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapValueN() {
|
||||
result := collection.ChooseRandomMapValueN(map[int]int{1: 1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1]
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapKeyAndValue() {
|
||||
k, v := collection.ChooseRandomMapKeyAndValue(map[int]int{1: 1})
|
||||
fmt.Println(k, v)
|
||||
// Output:
|
||||
// 1 1
|
||||
}
|
||||
|
||||
func ExampleChooseRandomMapKeyAndValueN() {
|
||||
result := collection.ChooseRandomMapKeyAndValueN(map[int]int{1: 1}, 1)
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// map[1:1]
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChooseRandomSliceElementRepeatN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestChooseRandomSliceElementRepeatN_NonEmptySlice", []int{1, 2, 3}, 3},
|
||||
{"TestChooseRandomSliceElementRepeatN_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomSliceElementRepeatN(c.input, 3)
|
||||
if len(result) != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomIndexRepeatN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestChooseRandomIndexRepeatN_NonEmptySlice", []int{1, 2, 3}, 3},
|
||||
{"TestChooseRandomIndexRepeatN_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomIndexRepeatN(c.input, 3)
|
||||
if len(result) != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomSliceElement(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomSliceElement_NonEmptySlice", []int{1, 2, 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
{"TestChooseRandomSliceElement_EmptySlice", []int{}, map[int]bool{0: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomSliceElement(c.input)
|
||||
if !c.expected[result] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomIndex(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomIndex_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}},
|
||||
{"TestChooseRandomIndex_EmptySlice", []int{}, map[int]bool{-1: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomIndex(c.input)
|
||||
if !c.expected[result] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomSliceElementN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
}{
|
||||
{"TestChooseRandomSliceElementN_NonEmptySlice", []int{1, 2, 3}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := collection.ChooseRandomSliceElementN(c.input, 3)
|
||||
if !collection.AllInComparableSlice(actual, c.input) {
|
||||
t.Fatalf("%s failed, actual: %v, error: %s", c.name, actual, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomIndexN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomIndexN_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomIndexN(c.input, 3)
|
||||
if !c.expected[result[0]] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapKeyRepeatN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapKeyRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomMapKeyRepeatN(c.input, 3)
|
||||
if !c.expected[result[0]] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapValueRepeatN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomMapValueRepeatN(c.input, 3)
|
||||
if !c.expected[result[0]] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapKeyAndValueRepeatN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapKeyAndValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomMapKeyAndValueRepeatN(c.input, 3)
|
||||
if !c.expected[result[1]] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapKey(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
{"TestChooseRandomMapKey_EmptyMap", map[int]int{}, map[int]bool{0: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomMapKey(c.input)
|
||||
if !c.expected[result] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapValue(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
{"TestChooseRandomMapValue_EmptyMap", map[int]int{}, map[int]bool{0: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomMapValue(c.input)
|
||||
if !c.expected[result] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapValueN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ChooseRandomMapValueN(c.input, 3)
|
||||
if !c.expected[result[0]] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapKeyAndValue(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapKeyAndValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
{"TestChooseRandomMapKeyAndValue_EmptyMap", map[int]int{}, map[int]bool{0: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
k, v := collection.ChooseRandomMapKeyAndValue(c.input)
|
||||
if !c.expected[k] || !c.expected[v] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChooseRandomMapKeyAndValueN(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input map[int]int
|
||||
expected map[int]bool
|
||||
}{
|
||||
{"TestChooseRandomMapKeyAndValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
kvm := collection.ChooseRandomMapKeyAndValueN(c.input, 1)
|
||||
for k := range kvm {
|
||||
if !c.expected[k] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func ExampleDescBy() {
|
||||
var slice = []int{1, 2, 3}
|
||||
sort.Slice(slice, func(i, j int) bool {
|
||||
return collection.DescBy(slice[i], slice[j])
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [3 2 1]
|
||||
}
|
||||
|
||||
func ExampleAscBy() {
|
||||
var slice = []int{1, 2, 3}
|
||||
sort.Slice(slice, func(i, j int) bool {
|
||||
return collection.AscBy(slice[i], slice[j])
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [1 2 3]
|
||||
}
|
||||
|
||||
func ExampleDesc() {
|
||||
var slice = []int{1, 2, 3}
|
||||
collection.Desc(&slice, func(index int) int {
|
||||
return slice[index]
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [3 2 1]
|
||||
}
|
||||
|
||||
func ExampleDescByClone() {
|
||||
var slice = []int{1, 2, 3}
|
||||
result := collection.DescByClone(slice, func(index int) int {
|
||||
return slice[index]
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [3 2 1]
|
||||
}
|
||||
|
||||
func ExampleAsc() {
|
||||
var slice = []int{1, 2, 3}
|
||||
collection.Asc(&slice, func(index int) int {
|
||||
return slice[index]
|
||||
})
|
||||
fmt.Println(slice)
|
||||
// Output:
|
||||
// [1 2 3]
|
||||
}
|
||||
|
||||
func ExampleAscByClone() {
|
||||
var slice = []int{1, 2, 3}
|
||||
result := collection.AscByClone(slice, func(index int) int {
|
||||
return slice[index]
|
||||
})
|
||||
fmt.Println(result)
|
||||
// Output:
|
||||
// [1 2 3]
|
||||
}
|
||||
|
||||
func ExampleShuffle() {
|
||||
var slice = []int{1, 2, 3}
|
||||
collection.Shuffle(&slice)
|
||||
fmt.Println(len(slice))
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleShuffleByClone() {
|
||||
var slice = []int{1, 2, 3}
|
||||
result := collection.ShuffleByClone(slice)
|
||||
fmt.Println(len(result))
|
||||
// Output:
|
||||
// 3
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package collection_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/collection"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDescBy(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestDescBy_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
|
||||
{"TestDescBy_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
sort.Slice(c.input, func(i, j int) bool {
|
||||
return collection.DescBy(c.input[i], c.input[j])
|
||||
})
|
||||
for i, v := range c.input {
|
||||
if v != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAscBy(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestAscBy_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
|
||||
{"TestAscBy_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
sort.Slice(c.input, func(i, j int) bool {
|
||||
return collection.AscBy(c.input[i], c.input[j])
|
||||
})
|
||||
for i, v := range c.input {
|
||||
if v != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesc(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestDesc_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
|
||||
{"TestDesc_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.Desc(&c.input, func(index int) int {
|
||||
return c.input[index]
|
||||
})
|
||||
for i, v := range c.input {
|
||||
if v != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescByClone(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestDescByClone_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
|
||||
{"TestDescByClone_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.DescByClone(c.input, func(index int) int {
|
||||
return c.input[index]
|
||||
})
|
||||
for i, v := range result {
|
||||
if v != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsc(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestAsc_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
|
||||
{"TestAsc_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.Asc(&c.input, func(index int) int {
|
||||
return c.input[index]
|
||||
})
|
||||
for i, v := range c.input {
|
||||
if v != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAscByClone(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{"TestAscByClone_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
|
||||
{"TestAscByClone_EmptySlice", []int{}, []int{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.AscByClone(c.input, func(index int) int {
|
||||
return c.input[index]
|
||||
})
|
||||
for i, v := range result {
|
||||
if v != c.expected[i] {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShuffle(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestShuffle_NonEmptySlice", []int{1, 2, 3}, 3},
|
||||
{"TestShuffle_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
collection.Shuffle(&c.input)
|
||||
if len(c.input) != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShuffleByClone(t *testing.T) {
|
||||
var cases = []struct {
|
||||
name string
|
||||
input []int
|
||||
expected int
|
||||
}{
|
||||
{"TestShuffleByClone_NonEmptySlice", []int{1, 2, 3}, 3},
|
||||
{"TestShuffleByClone_EmptySlice", []int{}, 0},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := collection.ShuffleByClone(c.input)
|
||||
if len(result) != c.expected {
|
||||
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package loadbalancer
|
||||
|
||||
type RoundRobinItem[Id comparable] interface {
|
||||
// Id 返回唯一标识
|
||||
Id() Id
|
||||
// GetId 返回唯一标识
|
||||
GetId() Id
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package message
|
||||
|
||||
// Broker 消息核心的接口定义
|
||||
type Broker[I, T comparable] interface {
|
||||
Run()
|
||||
Close()
|
||||
Publish(topic T, event Event[I, T]) error
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package message
|
||||
|
||||
type Producer[T comparable] interface {
|
||||
GetTopic() T
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
# Astgo
|
||||
|
||||
[](https://pkg.go.dev/github.com/kercylan98/minotaur)
|
||||

|
||||
|
||||
暂无介绍...
|
||||
|
||||
|
||||
## 目录导航
|
||||
列出了该 `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
|
||||
}
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -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 // 注释
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue