go: Gale-Shapley Algorithm
项目结构/* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:01 # User : geovindu # Product : GoLand # Project : goalgorithms # File : config.go */ package config import os var ( BASE_DIR string OUTPUT_DIR string LOG_DIR string ) func init() { BASE_DIR, _ os.Getwd() OUTPUT_DIR BASE_DIR /output LOG_DIR BASE_DIR /logs _ os.MkdirAll(OUTPUT_DIR, 0755) _ os.MkdirAll(LOG_DIR, 0755) } // 全局匹配配置 var MATCH_CONFIG struct { REVERSE_MODE bool // true反向模式供给方求婚(企业优先) MAX_MATCH_ROUND int // 最大迭代轮次 ENABLE_PERF_LOG bool // 打印性能日志 EXPORT_EXCEL bool // 导出Excel EXPORT_PLOT bool // 生成可视化图片 }{ REVERSE_MODE: true, MAX_MATCH_ROUND: 20000, ENABLE_PERF_LOG: true, EXPORT_EXCEL: true, EXPORT_PLOT: true, } // 绘图配置 var PLOT_CONFIG struct { FontName string DPI int FigureWidth float64 FigureHeight float64 MaxDrawLinkCount int }{ FontName: SimHei, DPI: 150, FigureWidth: 14, FigureHeight: 7, MaxDrawLinkCount: 200, } /* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:12 # User : geovindu # Product : GoLand # Project : goalgorithms # File : entity.go */ package models import time // MatchSubject 匹配主体通用实体 type MatchSubject struct { ID string json:id Name string json:name Score float64 json:score Attrs map[string]float64 json:attrs //业务扩展属性 } // PreferenceList 偏好序列 type PreferenceList struct { OwnerID string json:owner_id RankedIDs []string json:ranked_ids //从高到低排序ID } // MatchResultItem 单条匹配结果 type MatchResultItem struct { ProposerID string json:proposer_id ProposerName string json:proposer_name AcceptorID string json:acceptor_id AcceptorName string json:acceptor_name MatchScore float64 json:match_score } // MatchOutput 统一输出载体 type MatchOutput struct { SceneName string json:scene_name ReverseMode bool json:reverse_mode MatchList []MatchResultItem json:match_list UnmatchedProposers []MatchSubject json:unmatched_proposers UnmatchedAcceptors []MatchSubject json:unmatched_acceptors TotalCostMs float64 json:total_cost_ms CreateTime time.Time json:create_time } /* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:23 # User : geovindu # Product : GoLand # Project : goalgorithms # File : gale_shapley.go */ package core import ( fmt goalgorithms/galeshapley/config goalgorithms/galeshapley/models time ) type GaleShapleySolver struct { ReverseMode bool maxRound int } func NewGaleShapleySolver(reverseMode bool) *GaleShapleySolver { return GaleShapleySolver{ ReverseMode: reverseMode, maxRound: config.MATCH_CONFIG.MAX_MATCH_ROUND, } } // Solve 执行GS稳定匹配 // 返回匹配map[求婚方ID]接收方ID,未匹配求婚方集合,未匹配接收方集合,耗时ms func (s *GaleShapleySolver) Solve(proposerPrefs, acceptorPrefs []models.PreferenceList) (map[string]string, map[string]bool, map[string]bool, float64) { start : time.Now() // 构建偏好缓存 propPrefMap : make(map[string][]string) for _, p : range proposerPrefs { propPrefMap[p.OwnerID] p.RankedIDs } accPrefMap : make(map[string][]string) accRankCache : make(map[string]map[string]int) for _, a : range acceptorPrefs { accPrefMap[a.OwnerID] a.RankedIDs accRankCache[a.OwnerID] make(map[string]int) for idx, sid : range a.RankedIDs { accRankCache[a.OwnerID][sid] idx } } // 初始化状态 freeProposers : make(map[string]bool) nextProposeIdx : make(map[string]int) for pid : range propPrefMap { freeProposers[pid] true nextProposeIdx[pid] 0 } matchAcceptor : make(map[string]string) // acceptor - proposer finalMatch : make(map[string]string) // proposer - acceptor roundCnt : 0 for len(freeProposers) 0 roundCnt s.maxRound { roundCnt var pid string // 取出任意一个自由求婚方 for k : range freeProposers { pid k break } delete(freeProposers, pid) prefList : propPrefMap[pid] idx : nextProposeIdx[pid] if idx len(prefList) { continue } targetAid : prefList[idx] nextProposeIdx[pid] if incumbent, exist : matchAcceptor[targetAid]; !exist { // 接收方单身 matchAcceptor[targetAid] pid finalMatch[pid] targetAid } else { // 已有配对比较优先级 rankNew : accRankCache[targetAid][pid] rankOld : accRankCache[targetAid][incumbent] if rankNew rankOld { // 更换匹配 freeProposers[incumbent] true matchAcceptor[targetAid] pid finalMatch[pid] targetAid delete(finalMatch, incumbent) } else { freeProposers[pid] true } } } // 计算未匹配 usedAcceptors : make(map[string]bool) for _, aid : range matchAcceptor { usedAcceptors[aid] true } unmatchedAcceptors : make(map[string]bool) for aid : range accPrefMap { if !usedAcceptors[aid] { unmatchedAcceptors[aid] true } } unmatchedProposers : freeProposers costMs : float64(time.Since(start).Microseconds()) / 1000.0 if config.MATCH_CONFIG.ENABLE_PERF_LOG { fmt.Printf([GS算法耗时] %.2f ms | 迭代轮数:%d\n, costMs, roundCnt) } return finalMatch, unmatchedProposers, unmatchedAcceptors, costMs } /* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:13 # User : geovindu # Product : GoLand # Project : goalgorithms # File : score_engine.go */ package service import ( goalgorithms/galeshapley/models sort ) type ScoreFunc func(self, candidate models.MatchSubject) float64 // BuildPreference 单个主体生成偏好列表 func BuildPreference(self models.MatchSubject, candidates []models.MatchSubject, scoreFunc ScoreFunc) models.PreferenceList { type pair struct { score float64 id string } var list []pair for _, c : range candidates { s : scoreFunc(self, c) list append(list, pair{score: s, id: c.ID}) } // 分数降序 sort.Slice(list, func(i, j int) bool { return list[i].score list[j].score }) ranked : make([]string, 0, len(list)) for _, item : range list { ranked append(ranked, item.id) } return models.PreferenceList{ OwnerID: self.ID, RankedIDs: ranked, } } // BatchGeneratePreference 批量生成偏好 func BatchGeneratePreference(main []models.MatchSubject, pool []models.MatchSubject, scoreFunc ScoreFunc) []models.PreferenceList { res : make([]models.PreferenceList, 0, len(main)) for _, item : range main { res append(res, BuildPreference(item, pool, scoreFunc)) } return res } // 四大场景打分函数 // 场景1工匠 ↔ 定制订单 func ScoreArtisanOrder(artisan, order models.MatchSubject) float64 { profit : order.Attrs[profit] skillMatch : order.Attrs[skill_match] return 0.6*profit 0.4*skillMatch } func ScoreOrderArtisan(order, artisan models.MatchSubject) float64 { delivery : artisan.Attrs[delivery_score] craft : artisan.Attrs[craft_level] return 0.5*delivery 0.5*craft } // 场景2销售顾问 ↔ 高端客户 func ScoreSalesCustomer(sales, customer models.MatchSubject) float64 { consume : customer.Attrs[consume_level] styleFit : customer.Attrs[style_fit] return 0.7*consume 0.3*styleFit } func ScoreCustomerSales(customer, sales models.MatchSubject) float64 { service : sales.Attrs[service_score] pro : sales.Attrs[profession] return 0.5*service 0.5*pro } // 场景3原料供应商 ↔ 加工厂采购 func ScoreSupplierFactory(supplier, factory models.MatchSubject) float64 { amount : factory.Attrs[order_amount] payScore : factory.Attrs[payment_score] return 0.7*amount 0.3*payScore } func ScoreFactorySupplier(factory, supplier models.MatchSubject) float64 { price : supplier.Attrs[price_score] stable : supplier.Attrs[stable_score] return 0.6*price 0.4*stable } // 场景4设计师 ↔ 新品需求 func ScoreDesignerDemand(designer, demand models.MatchSubject) float64 { bonus : demand.Attrs[project_bonus] familiar : demand.Attrs[style_familiar] return 0.65*bonus 0.35*familiar } func ScoreDemandDesigner(demand, designer models.MatchSubject) float64 { return designer.Attrs[success_rate] } /* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:25 # User : geovindu # Product : GoLand # Project : goalgorithms # File : jewelry_scene.go */ package service import ( fmt github.com/google/uuid goalgorithms/galeshapley/config goalgorithms/galeshapley/core goalgorithms/galeshapley/models math/rand time ) func init() { rand.Seed(time.Now().UnixNano()) } func generateID() string { return uuid.NewString()[:8] } // Scene1ArtisanOrder 工匠-定制订单 func Scene1ArtisanOrder(scale int) models.MatchOutput { fmt.Printf(\n【场景1工匠-定制珠宝订单匹配】规模:%d \n, scale) var artisans []models.MatchSubject var orders []models.MatchSubject for i : 0; i scale; i { art : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(工匠_%04d, i1), Score: randFloat(60, 95), Attrs: map[string]float64{ delivery_score: randFloat(50, 100), craft_level: randFloat(50, 100), }, } artisans append(artisans, art) od : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(定制订单_%04d, i1), Score: randFloat(50, 98), Attrs: map[string]float64{ profit: randFloat(10, 100), skill_match: randFloat(0, 100), }, } orders append(orders, od) } reverseMode : config.MATCH_CONFIG.REVERSE_MODE var proposers, acceptors []models.MatchSubject var propScore, accScore ScoreFunc if reverseMode { proposers, acceptors artisans, orders propScore, accScore ScoreArtisanOrder, ScoreOrderArtisan } else { proposers, acceptors orders, artisans propScore, accScore ScoreOrderArtisan, ScoreArtisanOrder } propPrefs : BatchGeneratePreference(proposers, acceptors, propScore) accPrefs : BatchGeneratePreference(acceptors, proposers, accScore) solver : core.NewGaleShapleySolver(reverseMode) matchMap, unmatchPropIDs, unmatchAccIDs, costMs : solver.Solve(propPrefs, accPrefs) return buildOutput( 工匠_定制订单, proposers, acceptors, matchMap, unmatchPropIDs, unmatchAccIDs, reverseMode, costMs, ) } // Scene2SalesCustomer 销售-高端客户 func Scene2SalesCustomer(scale int) models.MatchOutput { fmt.Printf(\n【场景2销售顾问-高端客户匹配】规模:%d \n, scale) var sales []models.MatchSubject var customers []models.MatchSubject for i : 0; i scale; i { s : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(销售_%04d, i1), Score: randFloat(60, 95), Attrs: map[string]float64{ service_score: randFloat(50, 100), profession: randFloat(50, 100), }, } sales append(sales, s) c : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(高端客户_%04d, i1), Score: randFloat(55, 99), Attrs: map[string]float64{ consume_level: randFloat(10, 100), style_fit: randFloat(0, 100), }, } customers append(customers, c) } reverseMode : config.MATCH_CONFIG.REVERSE_MODE var proposers, acceptors []models.MatchSubject var propScore, accScore ScoreFunc if reverseMode { proposers, acceptors sales, customers propScore, accScore ScoreSalesCustomer, ScoreCustomerSales } else { proposers, acceptors customers, sales propScore, accScore ScoreCustomerSales, ScoreSalesCustomer } propPrefs : BatchGeneratePreference(proposers, acceptors, propScore) accPrefs : BatchGeneratePreference(acceptors, proposers, accScore) solver : core.NewGaleShapleySolver(reverseMode) matchMap, unmatchPropIDs, unmatchAccIDs, costMs : solver.Solve(propPrefs, accPrefs) return buildOutput( 销售_高端客户, proposers, acceptors, matchMap, unmatchPropIDs, unmatchAccIDs, reverseMode, costMs, ) } // Scene3SupplierFactory 供应商-采购单 func Scene3SupplierFactory(scale int) models.MatchOutput { fmt.Printf(\n【场景3原料供应商-加工厂采购单】规模:%d \n, scale) var suppliers []models.MatchSubject var factories []models.MatchSubject for i : 0; i scale; i { sp : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(原料供应商_%04d, i1), Score: randFloat(60, 96), Attrs: map[string]float64{ price_score: randFloat(40, 100), stable_score: randFloat(40, 100), }, } suppliers append(suppliers, sp) fac : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(加工厂采购_%04d, i1), Score: randFloat(50, 97), Attrs: map[string]float64{ order_amount: randFloat(20, 100), payment_score: randFloat(30, 100), }, } factories append(factories, fac) } reverseMode : config.MATCH_CONFIG.REVERSE_MODE var proposers, acceptors []models.MatchSubject var propScore, accScore ScoreFunc if reverseMode { proposers, acceptors suppliers, factories propScore, accScore ScoreSupplierFactory, ScoreFactorySupplier } else { proposers, acceptors factories, suppliers propScore, accScore ScoreFactorySupplier, ScoreSupplierFactory } propPrefs : BatchGeneratePreference(proposers, acceptors, propScore) accPrefs : BatchGeneratePreference(acceptors, proposers, accScore) solver : core.NewGaleShapleySolver(reverseMode) matchMap, unmatchPropIDs, unmatchAccIDs, costMs : solver.Solve(propPrefs, accPrefs) return buildOutput( 原料供应商_加工厂, proposers, acceptors, matchMap, unmatchPropIDs, unmatchAccIDs, reverseMode, costMs, ) } // Scene4DesignerDemand 设计师-新品需求 func Scene4DesignerDemand(scale int) models.MatchOutput { fmt.Printf(\n【场景4珠宝设计师_新品开发需求】规模:%d \n, scale) var designers []models.MatchSubject var demands []models.MatchSubject for i : 0; i scale; i { des : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(设计师_%04d, i1), Score: randFloat(65, 96), Attrs: map[string]float64{ success_rate: randFloat(40, 100), }, } designers append(designers, des) dm : models.MatchSubject{ ID: generateID(), Name: fmt.Sprintf(新品项目_%04d, i1), Score: randFloat(55, 98), Attrs: map[string]float64{ project_bonus: randFloat(10, 90), style_familiar: randFloat(0, 100), }, } demands append(demands, dm) } reverseMode : config.MATCH_CONFIG.REVERSE_MODE var proposers, acceptors []models.MatchSubject var propScore, accScore ScoreFunc if reverseMode { proposers, acceptors designers, demands propScore, accScore ScoreDesignerDemand, ScoreDemandDesigner } else { proposers, acceptors demands, designers propScore, accScore ScoreDemandDesigner, ScoreDesignerDemand } propPrefs : BatchGeneratePreference(proposers, acceptors, propScore) accPrefs : BatchGeneratePreference(acceptors, proposers, accScore) solver : core.NewGaleShapleySolver(reverseMode) matchMap, unmatchPropIDs, unmatchAccIDs, costMs : solver.Solve(propPrefs, accPrefs) return buildOutput( 设计师_新品需求, proposers, acceptors, matchMap, unmatchPropIDs, unmatchAccIDs, reverseMode, costMs, ) } func buildOutput( sceneName string, proposers, acceptors []models.MatchSubject, matchMap map[string]string, unmatchPropIDs, unmatchAccIDs map[string]bool, reverseMode bool, costMs float64, ) models.MatchOutput { propMap : make(map[string]models.MatchSubject) for _, p : range proposers { propMap[p.ID] p } accMap : make(map[string]models.MatchSubject) for _, a : range acceptors { accMap[a.ID] a } var matchList []models.MatchResultItem for pid, aid : range matchMap { p : propMap[pid] a : accMap[aid] matchList append(matchList, models.MatchResultItem{ ProposerID: pid, ProposerName: p.Name, AcceptorID: aid, AcceptorName: a.Name, MatchScore: (p.Score a.Score) / 2.0, }) } // 未匹配 var unmatchProp []models.MatchSubject for id : range unmatchPropIDs { unmatchProp append(unmatchProp, propMap[id]) } var unmatchAcc []models.MatchSubject for id : range unmatchAccIDs { unmatchAcc append(unmatchAcc, accMap[id]) } fmt.Printf(匹配成功数量%d | 求婚方未匹配:%d | 接收方未匹配:%d\n, len(matchList), len(unmatchProp), len(unmatchAcc)) return models.MatchOutput{ SceneName: sceneName, ReverseMode: reverseMode, MatchList: matchList, UnmatchedProposers: unmatchProp, UnmatchedAcceptors: unmatchAcc, TotalCostMs: costMs, CreateTime: time.Now(), } } func randFloat(min, max float64) float64 { return min rand.Float64()*(max-min) } /* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:16 # User : geovindu # Product : GoLand # Project : goalgorithms # File : excel_exporter.go */ package utils import ( fmt github.com/xuri/excelize/v2 goalgorithms/galeshapley/config goalgorithms/galeshapley/models path/filepath time ) func ExportExcel(output models.MatchOutput) string { t : time.Now().Format(20060102_150405) fileName : fmt.Sprintf(匹配结果_%s_%s.xlsx, output.SceneName, t) savePath : filepath.Join(config.OUTPUT_DIR, fileName) f : excelize.NewFile() defer f.Close() // Sheet1 匹配明细 sheet1 : 匹配明细 _ f.SetSheetName(Sheet1, sheet1) headers : []string{求婚方ID, 求婚方名称, 接收方ID, 接收方名称, 匹配综合得分} for col, h : range headers { cell, _ : excelize.CoordinatesToCellName(col1, 1) _ f.SetCellValue(sheet1, cell, h) } for rowIdx, item : range output.MatchList { r : rowIdx 2 _ f.SetCellValue(sheet1, fmt.Sprintf(A%d, r), item.ProposerID) _ f.SetCellValue(sheet1, fmt.Sprintf(B%d, r), item.ProposerName) _ f.SetCellValue(sheet1, fmt.Sprintf(C%d, r), item.AcceptorID) _ f.SetCellValue(sheet1, fmt.Sprintf(D%d, r), item.AcceptorName) _ f.SetCellValue(sheet1, fmt.Sprintf(E%d, r), item.MatchScore) } // Sheet2 未匹配求婚方 sheet2 : 未匹配求婚方 _, _ f.NewSheet(sheet2) _ f.SetCellValue(sheet2, A1, ID) _ f.SetCellValue(sheet2, B1, 名称) _ f.SetCellValue(sheet2, C1, 综合分数) for idx, s : range output.UnmatchedProposers { r : idx 2 _ f.SetCellValue(sheet2, fmt.Sprintf(A%d, r), s.ID) _ f.SetCellValue(sheet2, fmt.Sprintf(B%d, r), s.Name) _ f.SetCellValue(sheet2, fmt.Sprintf(C%d, r), s.Score) } // Sheet3 未匹配接收方 sheet3 : 未匹配接收方 _, _ f.NewSheet(sheet3) _ f.SetCellValue(sheet3, A1, ID) _ f.SetCellValue(sheet3, B1, 名称) _ f.SetCellValue(sheet3, C1, 综合分数) for idx, s : range output.UnmatchedAcceptors { r : idx 2 _ f.SetCellValue(sheet3, fmt.Sprintf(A%d, r), s.ID) _ f.SetCellValue(sheet3, fmt.Sprintf(B%d, r), s.Name) _ f.SetCellValue(sheet3, fmt.Sprintf(C%d, r), s.Score) } // Sheet4 运行摘要 sheet4 : 运行摘要 _, _ f.NewSheet(sheet4) summary : [][]interface{}{ {场景名称, output.SceneName}, {反向模式(企业优先), output.ReverseMode}, {成功匹配数, len(output.MatchList)}, {未匹配求婚方, len(output.UnmatchedProposers)}, {未匹配接收方, len(output.UnmatchedAcceptors)}, {算法耗时(ms), fmt.Sprintf(%.2f, output.TotalCostMs)}, } for row, line : range summary { _ f.SetCellValue(sheet4, fmt.Sprintf(A%d, row1), line[0]) _ f.SetCellValue(sheet4, fmt.Sprintf(B%d, row1), line[1]) } _ f.SaveAs(savePath) fmt.Printf(\n✅ Excel已导出%s\n, savePath) return savePath } /* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:17 # User : geovindu # Product : GoLand # Project : goalgorithms # File : visualizer.go */ package utils import ( fmt goalgorithms/galeshapley/config goalgorithms/galeshapley/models image/color os path/filepath time golang.org/x/image/font/opentype plot gonum.org/v1/plot plotfont gonum.org/v1/plot/font gonum.org/v1/plot/plotter gonum.org/v1/plot/plotutil gonum.org/v1/plot/text gonum.org/v1/plot/vg plotdraw gonum.org/v1/plot/vg/draw ) func DrawAllCharts(out models.MatchOutput) ([]string, error) { var paths []string p1, err : drawScoreHist(out) if err ! nil { return nil, err } paths append(paths, p1) // 连线图文字渲染依赖字体生产环境可按需开启 fmt.Printf(✅ 图表生成完成%v\n, paths) return paths, nil } func loadChineseFont() (*plotfont.Cache, error) { fontPaths : []string{ C:\Windows\Fonts\msyh.ttc, // 微软雅黑 C:\Windows\Fonts\simsun.ttc, // 宋体 C:\Windows\Fonts\simhei.ttf, // 黑体 (如果存在) } for _, fontPath : range fontPaths { data, err : os.ReadFile(fontPath) if err ! nil { continue } var f *opentype.Font // 尝试解析 TTC 集合 if len(data) 4 string(data[:4]) ttcf { coll, err : opentype.ParseCollection(data) if err ! nil { continue } if coll.NumFonts() 0 { f, err coll.Font(0) if err ! nil { continue } } } else { // 尝试解析单个字体 f, err opentype.Parse(data) if err ! nil { continue } } if f nil { continue } face : plotfont.Face{ Font: plotfont.Font{ Typeface: ChineseFont, Style: 0, Weight: 400, Size: 14, }, Face: f, } cache : plotfont.NewCache(plotfont.Collection{face}) fmt.Printf( ✓ 成功加载中文字体: %s\n, fontPath) return cache, nil } return nil, fmt.Errorf(未找到中文字体) } func drawScoreHist(out models.MatchOutput) (string, error) { t : time.Now().Format(20060102_150405) name : fmt.Sprintf(得分分布_%s_%s.png, out.SceneName, t) savePath : filepath.Join(config.OUTPUT_DIR, name) p : plot.New() p.Title.Text fmt.Sprintf(【%s】匹配综合得分分布, out.SceneName) p.Title.Padding vg.Points(10) p.X.Label.Text 匹配综合得分 p.Y.Label.Text 匹配对数 fontCache, err : loadChineseFont() if err nil { handler : text.Plain{Fonts: fontCache} p.TextHandler handler titleFont : plotfont.Font{ Typeface: ChineseFont, Style: 0, Weight: 400, Size: 16, } labelFont : plotfont.Font{ Typeface: ChineseFont, Style: 0, Weight: 400, Size: 12, } p.Title.TextStyle text.Style{ Color: color.Black, Font: titleFont, XAlign: plotdraw.XCenter, YAlign: plotdraw.YTop, Handler: handler, } p.X.Label.TextStyle text.Style{ Color: color.Black, Font: labelFont, Handler: handler, } p.Y.Label.TextStyle text.Style{ Color: color.Black, Font: labelFont, Handler: handler, } } pts : make(plotter.Values, 0, len(out.MatchList)) for _, item : range out.MatchList { pts append(pts, item.MatchScore) } hist, err : plotter.NewHist(pts, 25) if err ! nil { return , err } hist.FillColor plotutil.Color(0) p.Add(hist) err p.Save(vg.Length(config.PLOT_CONFIG.FigureWidth)*vg.Inch, vg.Length(config.PLOT_CONFIG.FigureHeight)*vg.Inch, savePath) return savePath, err }调用/* # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 21:28 # User : geovindu # Product : GoLand # Project : goalgorithms # File : galeshapleybll.go */ package bll import ( goalgorithms/galeshapley/config goalgorithms/galeshapley/service goalgorithms/galeshapley/utils ) func GaleshapleyMain() { // 模式切换开关 // config.MATCH_CONFIG.REVERSE_MODE false //正向模式需求方求婚 config.MATCH_CONFIG.REVERSE_MODE true //反向模式供给方求婚企业收益优先 // 选择场景运行 result : service.Scene1ArtisanOrder(600) result2 : service.Scene2SalesCustomer(400) result3 : service.Scene3SupplierFactory(500) result4 : service.Scene4DesignerDemand(300) if config.MATCH_CONFIG.EXPORT_EXCEL { utils.ExportExcel(result) } if config.MATCH_CONFIG.EXPORT_EXCEL { utils.ExportExcel(result2) } if config.MATCH_CONFIG.EXPORT_EXCEL { utils.ExportExcel(result3) } if config.MATCH_CONFIG.EXPORT_EXCEL { utils.ExportExcel(result4) } if config.MATCH_CONFIG.EXPORT_PLOT { _, _ utils.DrawAllCharts(result) } if config.MATCH_CONFIG.EXPORT_PLOT { _, _ utils.DrawAllCharts(result2) } if config.MATCH_CONFIG.EXPORT_PLOT { _, _ utils.DrawAllCharts(result3) } if config.MATCH_CONFIG.EXPORT_PLOT { _, _ utils.DrawAllCharts(result4) } println(\n全部任务执行完成) }介绍了一个基于Gale-Shapley算法的稳定匹配系统实现由涂聚文有限公司开发。该系统包含以下核心组件算法实现通过GaleShapleySolver结构体实现稳定匹配算法支持正向/反向模式最大迭代轮次可配置。业务场景适配工匠-定制订单匹配销售顾问-高端客户匹配原料供应商-加工厂匹配设计师-新品需求匹配 每个场景提供独立的评分函数支持权重定制。功能特性自动生成测试数据多维度评分机制支持Excel结果导出可视化图表生成得分分布直方图技术架构采用Go语言开发支持MySQL/SQL Server/PostgreSQL/Oracle/Neo4j数据库模块化设计核心算法、业务逻辑、工具类分离系统通过配置开关控制运行模式企业优先/个人优先适用于各类双边匹配场景测试数据显示可高效处理数百规模的匹配任务。输出

相关新闻

AI聊天记录为何总失忆?大模型记忆机制与解决方案

AI聊天记录为何总失忆?大模型记忆机制与解决方案

1. 为什么你的AI聊天记录总是"失忆"?上周我帮一位朋友调试他的AI对话应用时,遇到一个典型问题:每次新开对话,AI就像得了健忘症,完全不记得之前的交流内容。这其实是当前大模型普遍存在的"记忆缺失"…

2026/7/31 2:46:24 阅读更多 →
Modbus RTU协议详解:从原理到实战的工业通信指南

Modbus RTU协议详解:从原理到实战的工业通信指南

1. 项目概述:从工业现场到数字世界的桥梁在工业自动化、楼宇自控、能源管理这些领域里,我们常常需要让一堆“哑巴”设备开口说话,把温度、压力、开关状态这些物理信号,变成计算机能理解、能处理的数据。这个“翻译”工作&#xff…

2026/7/31 2:46:23 阅读更多 →
Verilog延迟语句深度解析:从仿真原理到工程实践

Verilog延迟语句深度解析:从仿真原理到工程实践

1. 项目概述:Verilog延迟语句的深度解析在数字电路设计和硬件描述语言(HDL)的实践中,Verilog的延迟语句是一个既基础又充满陷阱的概念。很多刚接触FPGA或ASIC设计的朋友,包括我自己在早期项目里,都曾对#5这…

2026/7/31 2:45:23 阅读更多 →

最新新闻

深入解析C/C++内存函数:从memcpy到memmove的实战避坑指南

深入解析C/C++内存函数:从memcpy到memmove的实战避坑指南

1. 项目概述:从“会用”到“懂”的内存函数之旅“内存函数(memcpy、memmove、memset、memcmp)你真的懂了吗?”这个标题,乍一看像是在拷问C/C程序员的基础知识。确实,这几个函数几乎是每个开发者入门时就会接…

2026/7/31 3:19:34 阅读更多 →
Godot 4集成ONLYOFFICE:在游戏引擎内实现实时文档协作

Godot 4集成ONLYOFFICE:在游戏引擎内实现实时文档协作

1. 项目概述:当游戏引擎遇上在线文档如果你是一个使用Godot Engine 4的游戏开发者,无论是独立开发者还是小团队的一员,大概率都经历过这样的场景:游戏设计文档、角色设定、关卡策划案、甚至是代码注释和版本更新日志,散…

2026/7/31 3:19:34 阅读更多 →
ReliefF算法在MATLAB中的实现与特征选择应用

ReliefF算法在MATLAB中的实现与特征选择应用

1. ReliefF算法与特征选择概述在数据挖掘和机器学习领域,特征选择是提高模型性能的关键步骤。ReliefF算法作为经典的过滤式特征选择方法,通过评估特征对样本分类的贡献度来进行特征重要性排序。与常见的方差分析、卡方检验等方法不同,ReliefF…

2026/7/31 3:19:34 阅读更多 →
GridPlayer终极教程:5分钟掌握多窗口视频网格播放器,免费开源同步播放神器

GridPlayer终极教程:5分钟掌握多窗口视频网格播放器,免费开源同步播放神器

GridPlayer终极教程:5分钟掌握多窗口视频网格播放器,免费开源同步播放神器 【免费下载链接】gridplayer Play videos side-by-side 项目地址: https://gitcode.com/gh_mirrors/gr/gridplayer GridPlayer是一款基于VLC引擎的免费开源多窗口视频网格…

2026/7/31 3:19:34 阅读更多 →
基于FOMIAUKF的电池SOC高精度估计算法解析

基于FOMIAUKF的电池SOC高精度估计算法解析

1. 项目概述电池状态估计(State of Charge, SOC)是电池管理系统(BMS)中最核心也最具挑战性的任务之一。SOC作为反映电池剩余电量的关键参数,其精确估计直接影响着电池的使用效率、安全性和寿命。然而,电池作…

2026/7/31 3:19:34 阅读更多 →
Arduino开发双轨制:从Mind+图形化入门到Arduino IDE精通的实战指南

Arduino开发双轨制:从Mind+图形化入门到Arduino IDE精通的实战指南

1. 项目概述:Arduino软件生态的双轨制如果你刚开始接触Arduino,面对的第一个选择往往不是买哪块板子,而是用哪个软件来写代码。这就像你要做木工,得先选好顺手的锯子和刨子。Arduino的软件世界,目前主要有两大流派&…

2026/7/31 3:18:34 阅读更多 →

日新闻

物理复制比逻辑复制好在哪?数据库复制原理详解

物理复制比逻辑复制好在哪?数据库复制原理详解

数据库复制是把主库数据同步到备库的机制,分为逻辑复制和物理复制两种。逻辑复制传输的是 SQL 语句或行变更事件,物理复制传输的是存储引擎底层的物理日志。阿里云 PolarDB(云原生数据库)采用物理复制,在同步延迟、数据…

2026/7/31 0:00:34 阅读更多 →
BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/bi/Bilib…

2026/7/31 0:00:34 阅读更多 →
有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

当前,游戏行业的“DataAI融合”已从概念验证进入价值落地阶段。根据IDC 2025年数据,中国AI游戏云市场规模已达18.6亿元;同时,游戏研发环节AI渗透率高达86%,生成式AI内容普及率超过50%。面对庞大的市场,游戏…

2026/7/31 0:00:34 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/31 1:03:03 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻