feat: 新增 combination 包,用于数组组合筛选(抽离自 poker 包)

- 可根据评估函数筛选出最优、最差等组合,天然支持优先级筛选。
- 适用于提示出牌、最优解等内容,例如:扑克牌、麻将等
This commit is contained in:
kercylan98 2023-07-29 15:15:35 +08:00
parent ac43963a86
commit 48d9c11316
7 changed files with 468 additions and 0 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
package combination

View File

@ -0,0 +1,4 @@
package combination
type Item interface {
}

View File

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

View File

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