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