python: Gale-Shapley Algorithm II

发布时间:2026/7/31 6:19:56
python: Gale-Shapley Algorithm II # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:18 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : config.py 全局配置中心 正向/反向匹配模式一键切换 ReverseModeTrue → 反向模式供给方求婚【企业收益优先业务默认】 ReverseModeFalse → 正向模式需求方求婚 import os # 基础路径配置 BASE_DIR os.path.dirname(os.path.abspath(__file__)) OUTPUT_DIR os.path.join(BASE_DIR, output) LOG_DIR os.path.join(BASE_DIR, logs) # 创建目录 os.makedirs(OUTPUT_DIR, exist_okTrue) os.makedirs(LOG_DIR, exist_okTrue) MATCH_CONFIG { REVERSE_MODE: True, # 核心切换开关 MAX_MATCH_ROUND: 20000, # 最大迭代轮次防死锁 ENABLE_PERF_LOG: True, # 打印性能耗时 EXPORT_EXCEL: True, # 是否导出Excel EXPORT_PLOT: True, # 是否生成可视化图片【新增】 } # 绘图全局配置 PLOT_CONFIG { font_priority: [ # 中文字体优先级链自动探测系统可用字体 STXihei, # 华文细黑 Microsoft YaHei, # 微软雅黑 SimHei, # 黑体 SimSun, # 宋体 NSimSun, # 新宋体 STKaiti, # 华文楷体 STFangsong, # 华文仿宋 STSong, # 华文宋体 Microsoft JhengHei, # 微软正黑体 KaiTi, # 楷体 FangSong, # 仿宋 ], dpi: 150, figure_size: (14, 7), max_draw_link_count: 200, # 匹配连线图最多渲染200条上千条避免画布爆炸 } # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm pip install pydantic # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:19 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : entity_models.py from pydantic import BaseModel, Field from typing import List, Dict, Optional, Set import uuid class MatchSubject(BaseModel): 匹配主体通用基类 id: str name: str score: float Field(description主体综合基础得分) attrs: Dict Field(default_factorydict, description业务扩展属性) class Config: arbitrary_types_allowed True class PreferenceList(BaseModel): 偏好序列实体由高→低排序对象ID owner_id: str ranked_ids: List[str] Field(description偏好id序列) class MatchResultItem(BaseModel): 单条匹配结果 proposer_id: str proposer_name: str acceptor_id: str acceptor_name: str match_score: float class MatchOutput(BaseModel): 算法统一输出载体所有场景返回标准结构 scene_name: str reverse_mode: bool match_list: List[MatchResultItem] unmatched_proposers: List[MatchSubject] unmatched_acceptors: List[MatchSubject] total_cost_ms: Optional[float] None class Config: arbitrary_types_allowed True # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:21 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : gale_shapley_solver.py import time from typing import Dict, List, Set from GaleShapleyB.models.entity_models import PreferenceList from GaleShapleyB.config import MATCH_CONFIG class GaleShapleySolver: 高性能迭代式 Gale-Shapley 稳定匹配算法 支持正向 / 反向模式切换 支持上千规模主体无递归、低内存 def __init__(self, reverse_mode: bool): self.reverse_mode reverse_mode self.max_round MATCH_CONFIG[MAX_MATCH_ROUND] def solve( self, proposer_prefs: List[PreferenceList], acceptor_prefs: List[PreferenceList] ) - tuple[Dict[str, str], Set[str], Set[str], float]: :param proposer_prefs: :param acceptor_prefs: :return: match_dict{求婚id:接收id}, 未匹配求婚方,未匹配接收方,耗时ms start_time time.perf_counter() # 构建O(1)查询缓存 prop_pref_map: Dict[str, List[str]] {p.owner_id: p.ranked_ids for p in proposer_prefs} acc_pref_map: Dict[str, List[str]] {p.owner_id: p.ranked_ids for p in acceptor_prefs} # 接收方偏好排名缓存快速对比优先级 acc_rank_cache: Dict[str, Dict[str, int]] {} for aid, rank_list in acc_pref_map.items(): acc_rank_cache[aid] {sid: idx for idx, sid in enumerate(rank_list)} free_proposers set(prop_pref_map.keys()) next_propose_idx: Dict[str, int] {pid: 0 for pid in prop_pref_map.keys()} match_acceptor: Dict[str, str] dict() # acceptor - proposer final_match: Dict[str, str] dict() # proposer - acceptor round_cnt 0 while free_proposers and round_cnt self.max_round: round_cnt 1 pid free_proposers.pop() pref_list prop_pref_map[pid] next_idx next_propose_idx[pid] if next_idx len(pref_list): continue target_aid pref_list[next_idx] next_propose_idx[pid] 1 if target_aid not in match_acceptor: match_acceptor[target_aid] pid final_match[pid] target_aid else: incumbent_pid match_acceptor[target_aid] rank_new acc_rank_cache[target_aid].get(pid, 99999) rank_old acc_rank_cache[target_aid].get(incumbent_pid, 99999) if rank_new rank_old: free_proposers.add(incumbent_pid) match_acceptor[target_aid] pid final_match[pid] target_aid del final_match[incumbent_pid] else: free_proposers.add(pid) used_acceptors set(match_acceptor.keys()) unmatched_acceptors set(acc_pref_map.keys()) - used_acceptors unmatched_proposers free_proposers cost_ms (time.perf_counter() - start_time) * 1000 if MATCH_CONFIG[ENABLE_PERF_LOG]: print(f[GS算法耗时] {cost_ms:.2f} ms | 迭代轮数:{round_cnt}) return final_match, unmatched_proposers, unmatched_acceptors, cost_ms # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:22 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : score_engine.py from typing import List, Callable from GaleShapleyB.models.entity_models import MatchSubject, PreferenceList class ScoreEngine: 通用打分引擎自动根据打分函数生成偏好序列 staticmethod def build_preference( self_subject: MatchSubject, candidate_list: List[MatchSubject], score_func: Callable[[MatchSubject, MatchSubject], float] ) - PreferenceList: :param self_subject: :param candidate_list: :param score_func: :return: score_pairs [] for candidate in candidate_list: score_val score_func(self_subject, candidate) score_pairs.append((-score_val, candidate.id)) score_pairs.sort() ranked_ids [cid for _, cid in score_pairs] return PreferenceList(owner_idself_subject.id, ranked_idsranked_ids) staticmethod def batch_generate_preference( main_list: List[MatchSubject], pool_list: List[MatchSubject], score_func: Callable[[MatchSubject, MatchSubject], float] ) - List[PreferenceList]: :param main_list: :param pool_list: :param score_func: :return: result [] for item in main_list: pref ScoreEngine.build_preference(item, pool_list, score_func) result.append(pref) return result # 珠宝四大场景打分规则 # 场景1工匠 - 定制订单 def score_artisan_order(artisan: MatchSubject, order: MatchSubject) - float: :param artisan: :param order: :return: profit order.attrs[profit] skill_match order.attrs[skill_match] return 0.6 * profit 0.4 * skill_match def score_order_artisan(order: MatchSubject, artisan: MatchSubject) - float: :param order: :param artisan: :return: delivery artisan.attrs[delivery_score] craft artisan.attrs[craft_level] return 0.5 * delivery 0.5 * craft # 场景2销售顾问 - 高端客户 def score_sales_customer(sales: MatchSubject, customer: MatchSubject) - float: :param sales: :param customer: :return: consume customer.attrs[consume_level] style customer.attrs[style_fit] return 0.7 * consume 0.3 * style def score_customer_sales(customer: MatchSubject, sales: MatchSubject) - float: :param customer: :param sales: :return: service sales.attrs[service_score] pro sales.attrs[profession] return 0.5 * service 0.5 * pro # 场景3原料供应商 - 加工厂采购单 def score_supplier_factory(supplier: MatchSubject, factory: MatchSubject) - float: :param supplier: :param factory: :return: amount factory.attrs[order_amount] pay_score factory.attrs[payment_score] return 0.7 * amount 0.3 * pay_score def score_factory_supplier(factory: MatchSubject, supplier: MatchSubject) - float: :param factory: :param supplier: :return: price supplier.attrs[price_score] stable supplier.attrs[stable_score] return 0.6 * price 0.4 * stable # 场景4设计师 - 新品开发需求 def score_designer_demand(designer: MatchSubject, demand: MatchSubject) - float: :param designer: :param demand: :return: bonus demand.attrs[project_bonus] familiar demand.attrs[style_familiar] return 0.65 * bonus 0.35 * familiar def score_demand_designer(demand: MatchSubject, designer: MatchSubject) - float: :param demand: :param designer: :return: return designer.attrs[success_rate] * 1.0 # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:26 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : jewelry_scene_service.py import uuid import random from GaleShapleyB.models.entity_models import MatchSubject, MatchOutput, MatchResultItem from GaleShapleyB.core.gale_shapley_solver import GaleShapleySolver from GaleShapleyB.service.score_engine import ScoreEngine from GaleShapleyB.config import MATCH_CONFIG from GaleShapleyB.service.score_engine import ( score_artisan_order, score_order_artisan, score_sales_customer, score_customer_sales, score_supplier_factory, score_factory_supplier, score_designer_demand, score_demand_designer ) class JewelrySceneService: 四大珠宝业务场景统一服务层 staticmethod def generate_id() - str: return str(uuid.uuid4())[:8] staticmethod def scene1_artisan_order(scale: int 500) - MatchOutput: :param scale: :return: print(f\n【场景1工匠-定制珠宝订单匹配】规模:{scale} ) artisans [] orders [] for i in range(scale): art MatchSubject( idJewelrySceneService.generate_id(), namef工匠_{i1:04d}, scoreround(random.uniform(60, 95), 2), attrs{ delivery_score: random.uniform(50, 100), craft_level: random.uniform(50, 100) } ) artisans.append(art) od MatchSubject( idJewelrySceneService.generate_id(), namef定制订单_{i1:04d}, scoreround(random.uniform(50, 98), 2), attrs{ profit: random.uniform(10, 100), skill_match: random.uniform(0, 100) } ) orders.append(od) reverse_mode MATCH_CONFIG[REVERSE_MODE] if reverse_mode: proposers, acceptors artisans, orders prop_score, acc_score score_artisan_order, score_order_artisan else: proposers, acceptors orders, artisans prop_score, acc_score score_order_artisan, score_artisan_order prop_prefs ScoreEngine.batch_generate_preference(proposers, acceptors, prop_score) acc_prefs ScoreEngine.batch_generate_preference(acceptors, proposers, acc_score) solver GaleShapleySolver(reverse_mode) match_dict, unmatch_prop, unmatch_acc, cost_ms solver.solve(prop_prefs, acc_prefs) return JewelrySceneService._build_output( scene_name工匠_定制订单, proposersproposers, acceptorsacceptors, match_dictmatch_dict, unmatch_prop_idsunmatch_prop, unmatch_acc_idsunmatch_acc, reverse_modereverse_mode, cost_mscost_ms ) staticmethod def scene2_sales_customer(scale: int 300) - MatchOutput: :param scale: :return: print(f\n【场景2销售顾问-高端客户匹配】规模:{scale} ) sales_list [] customer_list [] for i in range(scale): s MatchSubject( idJewelrySceneService.generate_id(), namef销售_{i1:04d}, scorerandom.uniform(60, 95), attrs{service_score: random.uniform(50, 100), profession: random.uniform(50, 100)} ) sales_list.append(s) c MatchSubject( idJewelrySceneService.generate_id(), namef高端客户_{i1:04d}, scorerandom.uniform(55, 99), attrs{consume_level: random.uniform(10, 100), style_fit: random.uniform(0, 100)} ) customer_list.append(c) reverse_mode MATCH_CONFIG[REVERSE_MODE] if reverse_mode: proposers, acceptors sales_list, customer_list prop_score, acc_score score_sales_customer, score_customer_sales else: proposers, acceptors customer_list, sales_list prop_score, acc_score score_customer_sales, score_sales_customer prop_prefs ScoreEngine.batch_generate_preference(proposers, acceptors, prop_score) acc_prefs ScoreEngine.batch_generate_preference(acceptors, proposers, acc_score) solver GaleShapleySolver(reverse_mode) match_dict, unmatch_prop, unmatch_acc, cost_ms solver.solve(prop_prefs, acc_prefs) return JewelrySceneService._build_output( scene_name销售_高端客户, proposersproposers, acceptorsacceptors, match_dictmatch_dict, unmatch_prop_idsunmatch_prop, unmatch_acc_idsunmatch_acc, reverse_modereverse_mode, cost_mscost_ms ) staticmethod def scene3_supplier_factory(scale: int 400) - MatchOutput: :param scale: :return: print(f\n【场景3原料供应商-加工厂采购单】规模:{scale} ) suppliers [] factories [] for i in range(scale): sp MatchSubject( idJewelrySceneService.generate_id(), namef原料供应商_{i1:04d}, scorerandom.uniform(60, 96), attrs{price_score: random.uniform(40, 100), stable_score: random.uniform(40, 100)} ) suppliers.append(sp) fac MatchSubject( idJewelrySceneService.generate_id(), namef加工厂采购_{i1:04d}, scorerandom.uniform(50, 97), attrs{order_amount: random.uniform(20, 100), payment_score: random.uniform(30, 100)} ) factories.append(fac) reverse_mode MATCH_CONFIG[REVERSE_MODE] if reverse_mode: proposers, acceptors suppliers, factories prop_score, acc_score score_supplier_factory, score_factory_supplier else: proposers, acceptors factories, suppliers prop_score, acc_score score_factory_supplier, score_supplier_factory prop_prefs ScoreEngine.batch_generate_preference(proposers, acceptors, prop_score) acc_prefs ScoreEngine.batch_generate_preference(acceptors, proposers, acc_score) solver GaleShapleySolver(reverse_mode) match_dict, unmatch_prop, unmatch_acc, cost_ms solver.solve(prop_prefs, acc_prefs) return JewelrySceneService._build_output( scene_name原料供应商_加工厂, proposersproposers, acceptorsacceptors, match_dictmatch_dict, unmatch_prop_idsunmatch_prop, unmatch_acc_idsunmatch_acc, reverse_modereverse_mode, cost_mscost_ms ) staticmethod def scene4_designer_demand(scale: int 200) - MatchOutput: :param scale: :return: print(f\n【场景4珠宝设计师_新品开发需求】规模:{scale} ) designers [] demands [] for i in range(scale): des MatchSubject( idJewelrySceneService.generate_id(), namef设计师_{i1:04d}, scorerandom.uniform(65, 96), attrs{success_rate: random.uniform(40, 100)} ) designers.append(des) dm MatchSubject( idJewelrySceneService.generate_id(), namef新品项目_{i1:04d}, scorerandom.uniform(55, 98), attrs{project_bonus: random.uniform(10, 90), style_familiar: random.uniform(0, 100)} ) demands.append(dm) reverse_mode MATCH_CONFIG[REVERSE_MODE] if reverse_mode: proposers, acceptors designers, demands prop_score, acc_score score_designer_demand, score_demand_designer else: proposers, acceptors demands, designers prop_score, acc_score score_demand_designer, score_designer_demand prop_prefs ScoreEngine.batch_generate_preference(proposers, acceptors, prop_score) acc_prefs ScoreEngine.batch_generate_preference(acceptors, proposers, acc_score) solver GaleShapleySolver(reverse_mode) match_dict, unmatch_prop, unmatch_acc, cost_ms solver.solve(prop_prefs, acc_prefs) return JewelrySceneService._build_output( scene_name设计师_新品需求, proposersdesigners, acceptorsdemands, match_dictmatch_dict, unmatch_prop_idsunmatch_prop, unmatch_acc_idsunmatch_acc, reverse_modereverse_mode, cost_mscost_ms ) staticmethod def _build_output( scene_name: str, proposers: list[MatchSubject], acceptors: list[MatchSubject], match_dict: dict[str, str], unmatch_prop_ids: set[str], unmatch_acc_ids: set[str], reverse_mode: bool, cost_ms: float ) - MatchOutput: :param scene_name: :param proposers: :param acceptors: :param match_dict: :param unmatch_prop_ids: :param unmatch_acc_ids: :param reverse_mode: :param cost_ms: :return: prop_map {x.id: x for x in proposers} acc_map {x.id: x for x in acceptors} result_items [] for pid, aid in match_dict.items(): p prop_map[pid] a acc_map[aid] item MatchResultItem( proposer_idpid, proposer_namep.name, acceptor_idaid, acceptor_namea.name, match_score(p.score a.score) / 2 ) result_items.append(item) unmatched_proposers [prop_map[uid] for uid in unmatch_prop_ids if uid in prop_map] unmatched_acceptors [acc_map[uid] for uid in unmatch_acc_ids if uid in acc_map] output MatchOutput( scene_namescene_name, reverse_modereverse_mode, match_listresult_items, unmatched_proposersunmatched_proposers, unmatched_acceptorsunmatched_acceptors, total_cost_mscost_ms ) print(f匹配成功数量{len(result_items)} | 求婚方未匹配:{len(unmatched_proposers)} | 接收方未匹配:{len(unmatched_acceptors)}) return output # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm pip install openpyxl # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:29 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : excel_exporter.py from openpyxl import Workbook from GaleShapleyB.models.entity_models import MatchOutput from datetime import datetime import os from GaleShapleyB.config import OUTPUT_DIR class ExcelExporter: 导出EXCEL文件 staticmethod def export_match_result(output: MatchOutput) - str: :param output: :return: time_str datetime.now().strftime(%Y%m%d_%H%M%S) file_name f匹配结果_{output.scene_name}_{time_str}.xlsx save_path os.path.join(OUTPUT_DIR, file_name) wb Workbook() ws1 wb.active ws1.title 匹配明细 headers [求婚方ID, 求婚方名称, 接收方ID, 接收方名称, 匹配综合得分] ws1.append(headers) for item in output.match_list: ws1.append([ item.proposer_id, item.proposer_name, item.acceptor_id, item.acceptor_name, round(item.match_score, 2) ]) ws2 wb.create_sheet(未匹配求婚方) ws2.append([ID, 名称, 综合分数]) for s in output.unmatched_proposers: ws2.append([s.id, s.name, round(s.score, 2)]) ws3 wb.create_sheet(未匹配接收方) ws3.append([ID, 名称, 综合分数]) for s in output.unmatched_acceptors: ws3.append([s.id, s.name, round(s.score, 2)]) ws4 wb.create_sheet(运行摘要) ws4.append([场景名称, output.scene_name]) ws4.append([反向模式(企业优先), output.reverse_mode]) ws4.append([成功匹配数, len(output.match_list)]) ws4.append([未匹配求婚方, len(output.unmatched_proposers)]) ws4.append([未匹配接收方, len(output.unmatched_acceptors)]) ws4.append([算法耗时(ms), f{output.total_cost_ms:.2f} if output.total_cost_ms else N/A]) wb.save(save_path) print(f\n✅ Excel已导出{save_path}) return save_path # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:31 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : plot_visualizer.py import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import matplotlib.pyplot as plt import numpy as np from datetime import datetime from GaleShapleyB.models.entity_models import MatchOutput from GaleShapleyB.config import OUTPUT_DIR, PLOT_CONFIG from common_utils.font_utils import setup_chinese_font # 使用字体优先级链配置 font_priority PLOT_CONFIG.get(font_priority, None) setup_chinese_font(font_priorityfont_priority, verboseTrue) class MatchVisualizer: 匹配结果可视化工具类 1. 匹配双方连线示意图限制最大连线防止上千数据卡死画布 2. 匹配得分分布直方图 staticmethod def draw_all_charts(match_output: MatchOutput) - list[str]: 一次性生成全部图表返回图片路径列表 :param match_output: :return: save_paths [] path1 MatchVisualizer.draw_score_hist(match_output) path2 MatchVisualizer.draw_match_link_graph(match_output) save_paths.extend([path1, path2]) print(f✅ 可视化图表生成完成{save_paths}) return save_paths staticmethod def draw_score_hist(match_output: MatchOutput) - str: 匹配综合得分分布直方图 :param match_output: :return: time_str datetime.now().strftime(%Y%m%d_%H%M%S) filename f得分分布_{match_output.scene_name}_{time_str}.png save_path os.path.join(OUTPUT_DIR, filename) scores [item.match_score for item in match_output.match_list] fig, ax plt.subplots(figsizePLOT_CONFIG[figure_size]) ax.hist(scores, bins25, color#2E86AB, alpha0.75, edgecolorblack) ax.set_title(f【{match_output.scene_name}】匹配综合得分分布, fontsize12) ax.set_xlabel(匹配综合得分) ax.set_ylabel(匹配对数) ax.grid(alpha0.3) plt.tight_layout() plt.savefig(save_path, dpiPLOT_CONFIG[dpi]) plt.close() return save_path staticmethod def draw_match_link_graph(match_output: MatchOutput) - str: 左右二分图匹配连线图限制最大绘制数量 :param match_output: :return: time_str datetime.now().strftime(%Y%m%d_%H%M%S) filename f匹配连线图_{match_output.scene_name}_{time_str}.png save_path os.path.join(OUTPUT_DIR, filename) max_draw PLOT_CONFIG[max_draw_link_count] draw_items match_output.match_list[:max_draw] fig, ax plt.subplots(figsizePLOT_CONFIG[figure_size]) left_x 1 right_x 5 count len(draw_items) for idx, item in enumerate(draw_items): y_pos count - idx ax.plot([left_x, right_x], [y_pos, y_pos], c#A23B72, alpha0.5, linewidth0.8) ax.text(left_x - 0.15, y_pos, item.proposer_name, fontsize6, vacenter, haright) ax.text(right_x 0.15, y_pos, item.acceptor_name, fontsize6, vacenter, haleft) ax.set_xlim(0, 6) ax.set_ylim(0, count 2) ax.set_title(f【{match_output.scene_name}】匹配二分连线图(最多渲染{max_draw}组), fontsize12) ax.axis(off) plt.tight_layout() plt.savefig(save_path, dpiPLOT_CONFIG[dpi]) plt.close() return save_path # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Matplotlib 中文字体自动探测与设置工具 # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # Project : PyAlgorithms # Module : font_utils.py Matplotlib 中文字体自动探测与设置工具 功能 1. 自动探测系统可用中文字体 2. 按优先级链选择最佳中文字体 3. 设置到 matplotlib rcParams 全局生效 4. 支持回退机制确保跨平台兼容 import matplotlib.pyplot as plt import matplotlib.font_manager as fm from typing import List, Optional # 默认字体优先级链Windows/macOS/Linux 通用 DEFAULT_FONT_PRIORITY [ # Windows 系统字体 STXihei, # 华文细黑 Microsoft YaHei, # 微软雅黑 SimHei, # 黑体 SimSun, # 宋体 NSimSun, # 新宋体 Microsoft JhengHei, # 微软正黑体 STKaiti, # 华文楷体 STFangsong, # 华文仿宋 STSong, # 华文宋体 # macOS 系统字体 PingFang SC, # 苹方简 PingFang TC, # 苹方繁 Heiti SC, # 黑体macOS Songti SC, # 宋体macOS Kaiti SC, # 楷体macOS # Linux 系统字体 Noto Sans CJK SC, # Noto Sans CJK 简体 Noto Sans CJK TC, # Noto Sans CJK 繁体 WenQuanYi Zen Hei, # 文泉驿正黑 WenQuanYi Micro Hei, # 文泉驿微米黑 AR PL UMing CN, # 文鼎 PL 宋体 # 通用字体 KaiTi, # 楷体 FangSong, # 仿宋 Arial Unicode MS, # Arial Unicode ] # 中文字体特征关键词用于回退匹配 CHINESE_FONT_KEYWORDS [ sim, hei, song, ming, kai, yahei, cjk, chinese, stxihei, stheiti, pingfang, heiti, noto, wenquanyi, uming, arial unicode ] def get_available_fonts() - set: 获取系统所有可用字体名称集合 :return: return {f.name for f in fm.fontManager.ttflist} def find_chinese_font(font_priority: Optional[List[str]] None) - Optional[str]: 按优先级链查找第一个可用的中文字体 Args: font_priority: 字体优先级列表为 None 时使用默认列表 Returns: 找到的字体名称未找到返回 None if font_priority is None: font_priority DEFAULT_FONT_PRIORITY available_fonts get_available_fonts() # 按优先级查找 for font_name in font_priority: if font_name in available_fonts: return font_name # 回退查找任何包含中文特征的字体 fallback_fonts [ f for f in available_fonts if any(k in f.lower() for k in CHINESE_FONT_KEYWORDS) ] if fallback_fonts: return fallback_fonts[0] return None def setup_chinese_font(font_priority: Optional[List[str]] None, verbose: bool True) - Optional[str]: 设置 matplotlib 中文字体全局生效 Args: font_priority: 字体优先级列表为 None 时使用默认列表 verbose: 是否打印字体设置信息 Returns: 实际使用的字体名称未找到中文字体返回 None font_name find_chinese_font(font_priority) if font_name: plt.rcParams[font.family] font_name plt.rcParams[axes.unicode_minus] False if verbose: print(f✅ [font_utils] 已设置 matplotlib 中文字体: {font_name}) else: plt.rcParams[axes.unicode_minus] False if verbose: print(⚠️ [font_utils] 未找到任何中文字体图表中可能出现中文乱码) print( 建议安装SimHei、Microsoft YaHei 或 Noto Sans CJK) return font_name def get_current_font() - str: 获取当前 matplotlib 使用的字体 :return: return plt.rcParams.get(font.family, DejaVu Sans) # 模块加载时自动执行字体设置确保全局生效 _setup_done False def auto_setup(): 模块加载时自动设置中文字体仅执行一次 :return: global _setup_done if not _setup_done: setup_chinese_font(verboseTrue) _setup_done True # 自动执行 auto_setup()调用# encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看言語成了邀功盡責的功臣還需要行爲每日來值班嗎 # 描述Gale-Shapley Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/7/30 20:33 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : GaleShapleyBBll.py from GaleShapleyB.service.jewelry_scene_service import JewelrySceneService from GaleShapleyB.utils.excel_exporter import ExcelExporter from GaleShapleyB.utils.plot_visualizer import MatchVisualizer from GaleShapleyB.config import MATCH_CONFIG class GaleShapleyBBll(object): def Demo(self): :return: # 模式切换 # MATCH_CONFIG[REVERSE_MODE] False # 正向需求方求婚 MATCH_CONFIG[REVERSE_MODE] True # 反向供给方求婚企业收益优先 # 选择执行场景自由开启/注释 result JewelrySceneService.scene1_artisan_order(scale600) # result JewelrySceneService.scene2_sales_customer(scale400) # result JewelrySceneService.scene3_supplier_factory(scale500) # result JewelrySceneService.scene4_designer_demand(scale300) # Excel导出 if MATCH_CONFIG[EXPORT_EXCEL]: ExcelExporter.export_match_result(result) # 生成可视化绘图【新增】 if MATCH_CONFIG[EXPORT_PLOT]: MatchVisualizer.draw_all_charts(result) print(\n全部任务执行完成)介绍了一个基于Gale-Shapley算法的稳定匹配系统实现主要包含以下内容算法核心模块实现了支持正向/反向模式切换的Gale-Shapley算法求解器可处理上千规模主体的匹配问题。业务场景适配针对珠宝行业设计了4个典型匹配场景工匠-订单、销售-客户、供应商-工厂、设计师-需求每个场景都有特定的打分规则。系统功能支持全局配置切换匹配模式正向/反向提供数据生成、匹配执行、结果输出全流程包含Excel导出和可视化图表生成功能自动处理中文字体显示问题技术特点使用Pydantic进行数据建模无递归迭代式算法实现支持大规模数据匹配模块化设计易于扩展新场景该系统可用于解决各类双边匹配问题特别是需要稳定匹配结果的业务场景。输出