From 48d9c1131627087b39456b9f376d3148942ad259 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 15:15:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20combination=20?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=95=B0=E7=BB=84=E7=BB=84?= =?UTF-8?q?=E5=90=88=E7=AD=9B=E9=80=89=EF=BC=88=E6=8A=BD=E7=A6=BB=E8=87=AA?= =?UTF-8?q?=20poker=20=E5=8C=85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 可根据评估函数筛选出最优、最差等组合,天然支持优先级筛选。 - 适用于提示出牌、最优解等内容,例如:扑克牌、麻将等 --- utils/combination/combination.go | 113 +++++++++++++ utils/combination/combination_options.go | 15 ++ utils/combination/combination_test.go | 50 ++++++ utils/combination/filters.go | 1 + utils/combination/item.go | 4 + utils/combination/matcher.go | 86 ++++++++++ utils/combination/matcher_options.go | 199 +++++++++++++++++++++++ 7 files changed, 468 insertions(+) create mode 100644 utils/combination/combination.go create mode 100644 utils/combination/combination_options.go create mode 100644 utils/combination/combination_test.go create mode 100644 utils/combination/filters.go create mode 100644 utils/combination/item.go create mode 100644 utils/combination/matcher.go create mode 100644 utils/combination/matcher_options.go diff --git a/utils/combination/combination.go b/utils/combination/combination.go new file mode 100644 index 0000000..35b9aa6 --- /dev/null +++ b/utils/combination/combination.go @@ -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 +} diff --git a/utils/combination/combination_options.go b/utils/combination/combination_options.go new file mode 100644 index 0000000..7edb62f --- /dev/null +++ b/utils/combination/combination_options.go @@ -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 + } +} diff --git a/utils/combination/combination_test.go b/utils/combination/combination_test.go new file mode 100644 index 0000000..d2f4c77 --- /dev/null +++ b/utils/combination/combination_test.go @@ -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) + } +} diff --git a/utils/combination/filters.go b/utils/combination/filters.go new file mode 100644 index 0000000..185bc35 --- /dev/null +++ b/utils/combination/filters.go @@ -0,0 +1 @@ +package combination diff --git a/utils/combination/item.go b/utils/combination/item.go new file mode 100644 index 0000000..86dbc2e --- /dev/null +++ b/utils/combination/item.go @@ -0,0 +1,4 @@ +package combination + +type Item interface { +} diff --git a/utils/combination/matcher.go b/utils/combination/matcher.go new file mode 100644 index 0000000..570b221 --- /dev/null +++ b/utils/combination/matcher.go @@ -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 +} diff --git a/utils/combination/matcher_options.go b/utils/combination/matcher_options.go new file mode 100644 index 0000000..9b521c6 --- /dev/null +++ b/utils/combination/matcher_options.go @@ -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 + }) + } +}