feat: 新增 combination 包,用于数组组合筛选(抽离自 poker 包)
- 可根据评估函数筛选出最优、最差等组合,天然支持优先级筛选。 - 适用于提示出牌、最优解等内容,例如:扑克牌、麻将等
This commit is contained in:
parent
ac43963a86
commit
48d9c11316
|
@ -0,0 +1,113 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
)
|
||||
|
||||
// NewCombination 创建一个新的组合器
|
||||
func NewCombination[T Item](options ...CombinationOption[T]) *Combination[T] {
|
||||
combination := &Combination[T]{
|
||||
matchers: make(map[string]*Matcher[T]),
|
||||
}
|
||||
for _, option := range options {
|
||||
option(combination)
|
||||
}
|
||||
if combination.evaluate == nil {
|
||||
combination.evaluate = func(items []T) float64 {
|
||||
return random.Float64()
|
||||
}
|
||||
}
|
||||
return combination
|
||||
}
|
||||
|
||||
// Combination 用于从多个匹配器内提取组合的数据结构
|
||||
type Combination[T Item] struct {
|
||||
evaluate func([]T) float64
|
||||
matchers map[string]*Matcher[T]
|
||||
priority []string
|
||||
}
|
||||
|
||||
// NewMatcher 添加一个新的匹配器
|
||||
func (slf *Combination[T]) NewMatcher(name string, options ...MatcherOption[T]) *Combination[T] {
|
||||
if _, exist := slf.matchers[name]; exist {
|
||||
panic("exist of the same matcher")
|
||||
}
|
||||
var matcher = &Matcher[T]{
|
||||
evaluate: slf.evaluate,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(matcher)
|
||||
}
|
||||
slf.matchers[name] = matcher
|
||||
slf.priority = append(slf.priority, name)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddMatcher 添加一个匹配器
|
||||
func (slf *Combination[T]) AddMatcher(name string, matcher *Matcher[T]) *Combination[T] {
|
||||
if _, exist := slf.matchers[name]; exist {
|
||||
panic("exist of the same matcher")
|
||||
}
|
||||
slf.matchers[name] = matcher
|
||||
slf.priority = append(slf.priority, name)
|
||||
return slf
|
||||
}
|
||||
|
||||
// RemoveMatcher 移除一个匹配器
|
||||
func (slf *Combination[T]) RemoveMatcher(name string) *Combination[T] {
|
||||
delete(slf.matchers, name)
|
||||
for i := 0; i < len(slf.priority); i++ {
|
||||
if slf.priority[i] == name {
|
||||
slf.priority = append(slf.priority[:i], slf.priority[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return slf
|
||||
}
|
||||
|
||||
// Combinations 从一组数据中提取所有符合匹配器规则的组合
|
||||
func (slf *Combination[T]) Combinations(items []T) (result [][]T) {
|
||||
for _, n := range slf.priority {
|
||||
result = append(result, slf.matchers[n].Combinations(items)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CombinationsToName 从一组数据中提取所有符合匹配器规则的组合,并返回匹配器名称
|
||||
func (slf *Combination[T]) CombinationsToName(items []T) (result map[string][][]T) {
|
||||
result = make(map[string][][]T)
|
||||
for _, n := range slf.priority {
|
||||
result[n] = append(result[n], slf.matchers[n].Combinations(items)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Best 从一组数据中提取符合匹配器规则的最佳组合
|
||||
func (slf *Combination[T]) Best(items []T) (name string, result []T) {
|
||||
var best float64 = -1
|
||||
for _, n := range slf.priority {
|
||||
matcher := slf.matchers[n]
|
||||
matcherBest := matcher.Best(items)
|
||||
if score := matcher.evaluate(matcherBest); score > best || best == -1 {
|
||||
best = score
|
||||
name = n
|
||||
result = matcherBest
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Worst 从一组数据中提取符合匹配器规则的最差组合
|
||||
func (slf *Combination[T]) Worst(items []T) (name string, result []T) {
|
||||
var worst float64 = -1
|
||||
for _, n := range slf.priority {
|
||||
matcher := slf.matchers[n]
|
||||
matcherWorst := matcher.Worst(items)
|
||||
if score := matcher.evaluate(matcherWorst); score < worst || worst == -1 {
|
||||
worst = score
|
||||
name = n
|
||||
result = matcherWorst
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package combination
|
||||
|
||||
// CombinationOption 组合器选项
|
||||
type CombinationOption[T Item] func(*Combination[T])
|
||||
|
||||
// WithCombinationEvaluation 设置组合评估函数
|
||||
// - 用于对组合进行评估,返回一个分值的评价函数
|
||||
// - 通过该选项将设置所有匹配器的默认评估函数为该函数
|
||||
// - 通过匹配器选项 WithMatcherEvaluation 可以覆盖该默认评估函数
|
||||
// - 默认的评估函数将返回一个随机数
|
||||
func WithCombinationEvaluation[T Item](evaluate func(items []T) float64) CombinationOption[T] {
|
||||
return func(c *Combination[T]) {
|
||||
c.evaluate = evaluate
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package combination_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/combination"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Poker struct {
|
||||
Point int
|
||||
Color int
|
||||
}
|
||||
|
||||
func TestCombination_Best(t *testing.T) {
|
||||
combine := combination.NewCombination(combination.WithCombinationEvaluation(func(items []*Poker) float64 {
|
||||
var total float64
|
||||
for _, item := range items {
|
||||
total += float64(item.Point)
|
||||
}
|
||||
return total
|
||||
}))
|
||||
|
||||
combine.NewMatcher("炸弹",
|
||||
combination.WithMatcherSame[*Poker, int](4, func(item *Poker) int {
|
||||
return item.Point
|
||||
}),
|
||||
).NewMatcher("三带一",
|
||||
combination.WithMatcherNCarryM[*Poker, int](3, 1, func(item *Poker) int {
|
||||
return item.Point
|
||||
}),
|
||||
)
|
||||
|
||||
var cards = []*Poker{
|
||||
{Point: 2, Color: 1},
|
||||
{Point: 2, Color: 2},
|
||||
{Point: 2, Color: 3},
|
||||
{Point: 3, Color: 4},
|
||||
{Point: 4, Color: 1},
|
||||
{Point: 4, Color: 2},
|
||||
{Point: 5, Color: 3},
|
||||
{Point: 6, Color: 4},
|
||||
{Point: 7, Color: 1},
|
||||
}
|
||||
|
||||
name, result := combine.Best(cards)
|
||||
fmt.Println("best:", name)
|
||||
for _, item := range result {
|
||||
fmt.Println(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package combination
|
|
@ -0,0 +1,4 @@
|
|||
package combination
|
||||
|
||||
type Item interface {
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
)
|
||||
|
||||
// NewMatcher 创建一个新的匹配器
|
||||
func NewMatcher[T Item](options ...MatcherOption[T]) *Matcher[T] {
|
||||
matcher := &Matcher[T]{
|
||||
filter: make([]func(items []T) [][]T, 0),
|
||||
}
|
||||
for _, option := range options {
|
||||
option(matcher)
|
||||
}
|
||||
if matcher.evaluate == nil {
|
||||
matcher.evaluate = func(items []T) float64 {
|
||||
return random.Float64()
|
||||
}
|
||||
}
|
||||
return matcher
|
||||
}
|
||||
|
||||
// Matcher 用于从一组数据内提取组合的数据结构
|
||||
type Matcher[T Item] struct {
|
||||
evaluate func(items []T) float64 // 用于对组合进行评估,返回一个分值的评价函数
|
||||
filter []func(items []T) [][]T // 用于对组合进行筛选的函数
|
||||
}
|
||||
|
||||
// AddFilter 添加一个筛选器
|
||||
// - 筛选器用于对组合进行筛选,返回一个二维数组,每个数组内的元素都是一个组合
|
||||
func (slf *Matcher[T]) AddFilter(filter func(items []T) [][]T) {
|
||||
slf.filter = append(slf.filter, filter)
|
||||
}
|
||||
|
||||
// Combinations 从一组数据中提取所有符合筛选器规则的组合
|
||||
func (slf *Matcher[T]) Combinations(items []T) [][]T {
|
||||
var combinations = [][]T{items}
|
||||
for _, handle := range slf.filter {
|
||||
combinations = append(combinations, handle(items)...)
|
||||
}
|
||||
return combinations
|
||||
}
|
||||
|
||||
// Best 从一组数据中提取符筛选器规则的最佳组合
|
||||
func (slf *Matcher[T]) Best(items []T) []T {
|
||||
var bestCombination = items
|
||||
|
||||
for _, handle := range slf.filter {
|
||||
var bestScore float64 = -1
|
||||
filteredCombinations := handle(bestCombination)
|
||||
if len(filteredCombinations) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, combination := range filteredCombinations {
|
||||
score := slf.evaluate(combination)
|
||||
if score > bestScore || bestScore == -1 {
|
||||
bestCombination = combination
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestCombination
|
||||
}
|
||||
|
||||
// Worst 从一组数据中提取符筛选器规则的最差组合
|
||||
func (slf *Matcher[T]) Worst(items []T) []T {
|
||||
var worstCombination = items
|
||||
|
||||
for _, handle := range slf.filter {
|
||||
var worstScore float64 = -1
|
||||
filteredCombinations := handle(worstCombination)
|
||||
if len(filteredCombinations) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, combination := range filteredCombinations {
|
||||
score := slf.evaluate(combination)
|
||||
if score < worstScore || worstScore == -1 {
|
||||
worstCombination = combination
|
||||
worstScore = score
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return worstCombination
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// MatcherOption 匹配器选项
|
||||
type MatcherOption[T Item] func(matcher *Matcher[T])
|
||||
|
||||
// WithMatcherEvaluation 设置匹配器评估函数
|
||||
// - 用于对组合进行评估,返回一个分值的评价函数
|
||||
// - 通过该选项将覆盖匹配器的默认(WithCombinationEvaluation)评估函数
|
||||
func WithMatcherEvaluation[T Item](evaluate func(items []T) float64) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.evaluate = evaluate
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherLeastLength 通过匹配最小长度的组合创建匹配器
|
||||
// - length: 组合的长度,表示需要匹配的组合最小数量
|
||||
func WithMatcherLeastLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, length, len(items))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherLength 通过匹配长度的组合创建匹配器
|
||||
// - length: 组合的长度,表示需要匹配的组合数量
|
||||
func WithMatcherLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, length, length)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherMostLength 通过匹配最大长度的组合创建匹配器
|
||||
// - length: 组合的长度,表示需要匹配的组合最大数量
|
||||
func WithMatcherMostLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, 1, length)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherIntervalLength 通过匹配长度区间的组合创建匹配器
|
||||
// - min: 组合的最小长度,表示需要匹配的组合最小数量
|
||||
// - max: 组合的最大长度,表示需要匹配的组合最大数量
|
||||
func WithMatcherIntervalLength[T Item](min, max int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, min, max)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherContinuity 通过匹配连续的组合创建匹配器
|
||||
// - index: 用于获取组合中元素的索引值,用于判断是否连续
|
||||
func WithMatcherContinuity[T Item, Index generic.Number](getIndex func(item T) Index) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
n := len(items)
|
||||
|
||||
if n <= 0 {
|
||||
return combinations
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return getIndex(items[i]) < getIndex(items[j])
|
||||
})
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
combination := []T{items[i]}
|
||||
for j := i + 1; j < n; j++ {
|
||||
if getIndex(items[j])-getIndex(combination[len(combination)-1]) == Index(1) {
|
||||
combination = append(combination, items[j])
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(combination) >= 2 {
|
||||
combinations = append(combinations, combination)
|
||||
}
|
||||
}
|
||||
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherSame 通过匹配相同的组合创建匹配器
|
||||
// - count: 组合中相同元素的数量,当 count <= 0 时,表示相同元素的数量不限
|
||||
// - getType: 用于获取组合中元素的类型,用于判断是否相同
|
||||
func WithMatcherSame[T Item, E generic.Ordered](count int, getType func(item T) E) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := slice.LimitedCombinations(items, count, count)
|
||||
for _, items := range groups {
|
||||
if count > 0 && len(items) != count {
|
||||
continue
|
||||
}
|
||||
var e E
|
||||
var init = true
|
||||
var same = true
|
||||
for _, item := range items {
|
||||
if init {
|
||||
init = false
|
||||
e = getType(item)
|
||||
} else if getType(item) != e {
|
||||
same = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if same {
|
||||
combinations = append(combinations, items)
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherNCarryM 通过匹配 N 携带 M 的组合创建匹配器
|
||||
// - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同
|
||||
// - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型需要全部相同
|
||||
// - getType: 用于获取组合中元素的类型,用于判断是否相同
|
||||
func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T) E) MatcherOption[T] {
|
||||
return func(matcher *Matcher[T]) {
|
||||
matcher.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
|
||||
groups := make(map[E][]T)
|
||||
for _, item := range items {
|
||||
itemType := getType(item)
|
||||
groups[itemType] = append(groups[itemType], item)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group) != n {
|
||||
continue
|
||||
}
|
||||
ms := slice.Combinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) }))
|
||||
for i := 0; i < len(ms); i++ {
|
||||
ts := make(map[E][]T)
|
||||
for _, t := range ms[i] {
|
||||
tt := getType(t)
|
||||
ts[tt] = append(ts[tt], t)
|
||||
}
|
||||
for _, cs := range ts {
|
||||
if len(cs) == m {
|
||||
combinations = append(combinations, slice.Merge(group, cs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherNCarryIndependentM 通过匹配 N 携带独立 M 的组合创建匹配器
|
||||
// - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同
|
||||
// - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型无需全部相同
|
||||
// - getType: 用于获取组合中元素的类型,用于判断是否相同
|
||||
func WithMatcherNCarryIndependentM[T Item, E generic.Ordered](n, m int, getType func(item T) E) MatcherOption[T] {
|
||||
return func(matcher *Matcher[T]) {
|
||||
matcher.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
|
||||
groups := make(map[E][]T)
|
||||
for _, item := range items {
|
||||
itemType := getType(item)
|
||||
groups[itemType] = append(groups[itemType], item)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group) != n {
|
||||
continue
|
||||
}
|
||||
ms := slice.Combinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) }))
|
||||
for i := 0; i < len(ms); i++ {
|
||||
is := ms[i]
|
||||
if len(is) == m {
|
||||
combinations = append(combinations, slice.Merge(group, is))
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue