Skip to content

Commit

Permalink
feat: 新增 combination 包,用于数组组合筛选(抽离自 poker 包)
Browse files Browse the repository at this point in the history
- 可根据评估函数筛选出最优、最差等组合,天然支持优先级筛选。
- 适用于提示出牌、最优解等内容,例如:扑克牌、麻将等
  • Loading branch information
kercylan98 committed Jul 29, 2023
1 parent ac43963 commit 48d9c11
Show file tree
Hide file tree
Showing 7 changed files with 468 additions and 0 deletions.
113 changes: 113 additions & 0 deletions utils/combination/combination.go
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions utils/combination/combination_options.go
Original file line number Diff line number Diff line change
@@ -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
}
}
50 changes: 50 additions & 0 deletions utils/combination/combination_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions utils/combination/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package combination
4 changes: 4 additions & 0 deletions utils/combination/item.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package combination

type Item interface {
}
86 changes: 86 additions & 0 deletions utils/combination/matcher.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 48d9c11

Please sign in to comment.