-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 新增 combination 包,用于数组组合筛选(抽离自 poker 包)
- 可根据评估函数筛选出最优、最差等组合,天然支持优先级筛选。 - 适用于提示出牌、最优解等内容,例如:扑克牌、麻将等
- Loading branch information
1 parent
ac43963
commit 48d9c11
Showing
7 changed files
with
468 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package combination |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package combination | ||
|
||
type Item interface { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.