项目结构/* # 版权所有 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数据库模块化设计核心算法、业务逻辑、工具类分离系统通过配置开关控制运行模式企业优先/个人优先适用于各类双边匹配场景测试数据显示可高效处理数百规模的匹配任务。输出
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数据库模块化设计核心算法、业务逻辑、工具类分离系统通过配置开关控制运行模式企业优先/个人优先适用于各类双边匹配场景测试数据显示可高效处理数百规模的匹配任务。输出