项目结构:
# encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 20:08 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : app_config.py """ 全局应用配置 """ import os class AppConfig: # 日志配置 LOG_SAVE_PATH = "./logs" LOG_FILE_NAME = "jewelry_packer.log" LOG_CONSOLE_ENABLE = True LOG_FILE_ENABLE = True # 并发设置 MAX_WORKER_THREAD = 8 # 配载搜索最大状态数,防止极端情况无限搜索 MAX_SEARCH_STATE = 15000 @staticmethod def init_log_dir(): """ 初始化日志文件夹 :return: """ if not os.path.exists(AppConfig.LOG_SAVE_PATH): os.makedirs(AppConfig.LOG_SAVE_PATH, exist_ok=True) # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 20:08 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : enums.py """ 系统枚举定义 """ from enum import Enum class LoadStrategyType(Enum): """ 配载算法策略类型 """ BFS = "BFS" # 最优方案,最少包装数量(主推) DFS = "DFS" # 深度优先,可行方案,不一定最优 ASTAR = "ASTAR" # A*启发搜索 class ResultCode(Enum): """ 业务返回码 """ SUCCESS = 0, "成功" PARAM_ERROR = 400, "参数非法" NO_AVAILABLE_PACKAGE = 401, "无可用包装规格" SEARCH_OVER_LIMIT = 501, "搜索状态超限,未找到方案" NO_ANY_LOAD_PLAN = 502, "无法生成配载方案" # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 20:08 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : models.py """ 数据实体模型 DTO """ from dataclasses import dataclass from typing import Dict, List, Optional @dataclass class PackageSpec: """ 包装规格实体 """ spec_code: str # 包装编码:BOX-500 volume: float # 包装容积 cm³ stock_qty: int # 当前可用库存数量 @dataclass class PackQueryCondition: """ 配载查询入参 """ total_material_volume: float # 首饰+配件总体积 cm³ package_spec_list: List[PackageSpec] # 可用包装规格列表 strategy_type: str # 算法策略 allow_over_load: bool = False # 是否允许包装内超容(默认false) @dataclass class SingleUsePackage: """ 方案内使用的包装记录 """ spec_code: str volume: float use_count: int @dataclass class PackResult: """ 配载最终输出结果 """ code: int msg: str use_package_list: List[SingleUsePackage] # 本次选用包装清单 remain_material_volume: float # 剩余未装入物料体积 remain_package_stock: Dict[str, int] # 各规格剩余库存 {spec_code:剩余数量} total_use_package_count: int # 一共使用多少个包装 search_state_count: int # 算法搜索遍历状态数量 full_load_flag: bool # True=物料全部装完,无剩余 # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 20:09 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : logger.py """ 统一日志组件 满足:控制台与日志文件输出格式完全一致,线程安全 """ import logging import sys from BreadthFirst.config.app_config import AppConfig import os import threading from typing import Optional from logging.handlers import RotatingFileHandler class AppLogger: _instance: Optional[logging.Logger] = None # 【修复】使用线程锁,而非原生object _lock = threading.Lock() @classmethod def get_logger(cls) -> logging.Logger: """ 单例获取日志实例,线程安全 :return: """ if cls._instance is None: with cls._lock: if cls._instance is None: cls._init_logger() return cls._instance @classmethod def _init_logger(cls): """ :return: """ AppConfig.init_log_dir() logger = logging.getLogger("JewelryPacker") logger.setLevel(logging.INFO) logger.propagate = False format_str = "%(asctime)s [%(levelname)s] %(message)s" formatter = logging.Formatter(format_str, datefmt="%Y-%m-%d %H:%M:%S") # 控制台输出 if AppConfig.LOG_CONSOLE_ENABLE: console_handler = logging.StreamHandler(stream=sys.stdout) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # 文件滚动日志 if AppConfig.LOG_FILE_ENABLE: log_path = os.path.join(AppConfig.LOG_SAVE_PATH, AppConfig.LOG_FILE_NAME) file_handler = RotatingFileHandler( filename=log_path, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8" ) file_handler.setFormatter(formatter) logger.addHandler(file_handler) cls._instance = logger # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 20:10 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : strategy_base.py """ 配载策略抽象基类 所有算法统一实现该接口,方便无缝切换策略,符合开闭原则 """ from abc import ABC, abstractmethod from BreadthFirst.core.models import PackQueryCondition, PackResult class BasePackStrategy(ABC): """ 包装配载策略抽象父类 """ @abstractmethod def execute(self, condition: PackQueryCondition) -> PackResult: """ 执行配载计算 :param condition: 查询条件 :return: 配载结果对象 """ pass # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 21:01 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : strategy_bfs.py """ BFS配载最优策略 目标:使用最少数量礼盒装载物料,等价分水问题最短路径搜索 """ from collections import deque from typing import Tuple, Dict from BreadthFirst.core.strategy_base import BasePackStrategy from BreadthFirst.core.models import PackQueryCondition, PackResult, SingleUsePackage from BreadthFirst.core.enums import ResultCode from BreadthFirst.config.app_config import AppConfig from BreadthFirst.core.logger import AppLogger logger = AppLogger.get_logger() class BFSPackStrategy(BasePackStrategy): def execute(self, condition: PackQueryCondition) -> PackResult: """ BFS搜索最优包装组合方案 """ total_mat_vol = condition.total_material_volume spec_list = condition.package_spec_list allow_over = condition.allow_over_load max_state = AppConfig.MAX_SEARCH_STATE # ==========1. 参数校验 ========== if total_mat_vol <= 0: return PackResult( code=ResultCode.PARAM_ERROR.value[0], msg="物料总体积必须大于0", use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock={}, total_use_package_count=0, search_state_count=0, full_load_flag=False ) if len(spec_list) == 0: return PackResult( code=ResultCode.NO_AVAILABLE_PACKAGE.value[0], msg=ResultCode.NO_AVAILABLE_PACKAGE.value[1], use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock={}, total_use_package_count=0, search_state_count=0, full_load_flag=False ) # 初始化包装库存字典 init_stock: Dict[str, int] = {} spec_volume_map: Dict[str, float] = {} for item in spec_list: init_stock[item.spec_code] = item.stock_qty spec_volume_map[item.spec_code] = item.volume # 状态定义:(当前已装载体积, 当前各包装消耗字典) start_load_vol = 0.0 start_used: Dict[str, int] = {code: 0 for code in init_stock.keys()} visited = set() visited_count = 1 # 队列元素:已装载体积,已使用包装计数字典 queue = deque() queue.append((start_load_vol, start_used)) visited.add((start_load_vol, tuple(start_used.values()))) best_solution = None while queue: load_vol, used_dict = queue.popleft() # 判断当前方案是否满足装载需求 if load_vol >= total_mat_vol: best_solution = (load_vol, used_dict) break if visited_count >= max_state: logger.warning(f"搜索达到最大状态限制{max_state},终止搜索") break # 遍历所有包装规格尝试放入一个礼盒 for spec_code, stock_num in init_stock.items(): used_num = used_dict[spec_code] if used_num >= stock_num: continue # 该规格库存耗尽,不能继续选 pack_vol = spec_volume_map[spec_code] new_load_vol = load_vol + pack_vol # 不允许超载并且新容积超出,则跳过 if not allow_over and new_load_vol > total_mat_vol: continue new_used = used_dict.copy() new_used[spec_code] += 1 state_key = (new_load_vol, tuple(new_used.values())) if state_key not in visited: visited.add(state_key) visited_count += 1 queue.append((new_load_vol, new_used)) # ==========2. 方案结果组装 ========== if best_solution is None: return PackResult( code=ResultCode.NO_ANY_LOAD_PLAN.value[0], msg="未找到可行包装组合方案", use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock=init_stock.copy(), total_use_package_count=0, search_state_count=visited_count, full_load_flag=False ) final_load_vol, final_used = best_solution remain_material = round(total_mat_vol - final_load_vol, 2) full_flag = remain_material <= 0.001 use_package_list = [] remain_stock = {} for spec_code, total_stock in init_stock.items(): use_cnt = final_used[spec_code] remain_cnt = total_stock - use_cnt remain_stock[spec_code] = remain_cnt if use_cnt > 0: use_package_list.append(SingleUsePackage( spec_code=spec_code, volume=spec_volume_map[spec_code], use_count=use_cnt )) total_use = sum([x.use_count for x in use_package_list]) return PackResult( code=ResultCode.SUCCESS.value[0], msg="配载方案计算成功", use_package_list=use_package_list, remain_material_volume=abs(remain_material), remain_package_stock=remain_stock, total_use_package_count=total_use, search_state_count=visited_count, full_load_flag=full_flag ) # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 21:02 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : strategy_dfs.py """ DFS配载策略(备选) 不保证最少包装数量,仅作方案对比 """ from BreadthFirst.core.strategy_base import BasePackStrategy from BreadthFirst.core.models import PackQueryCondition, PackResult, SingleUsePackage from BreadthFirst.core.enums import ResultCode from BreadthFirst.config.app_config import AppConfig from BreadthFirst.core.logger import AppLogger logger = AppLogger.get_logger() class DFSPackStrategy(BasePackStrategy): """ """ def execute(self, condition: PackQueryCondition) -> PackResult: """ :param condition: :return: """ total_mat_vol = condition.total_material_volume spec_list = condition.package_spec_list allow_over = condition.allow_over_load max_state = AppConfig.MAX_SEARCH_STATE if total_mat_vol <= 0: return PackResult( code=ResultCode.PARAM_ERROR.value[0], msg="物料总体积必须大于0", use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock={}, total_use_package_count=0, search_state_count=0, full_load_flag=False ) if len(spec_list) == 0: return PackResult( code=ResultCode.NO_AVAILABLE_PACKAGE.value[0], msg=ResultCode.NO_AVAILABLE_PACKAGE.value[1], use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock={}, total_use_package_count=0, search_state_count=0, full_load_flag=False ) init_stock = {} spec_volume_map = {} for item in spec_list: init_stock[item.spec_code] = item.stock_qty spec_volume_map[item.spec_code] = item.volume visited = set() visited_count = 1 best_solution = None def dfs(load_vol, used_dict): nonlocal visited_count, best_solution if best_solution is not None: return True if load_vol >= total_mat_vol: best_solution = (load_vol, used_dict.copy()) return True state_key = (load_vol, tuple(used_dict.values())) if state_key in visited: return False if visited_count >= max_state: return False visited.add(state_key) visited_count += 1 for spec_code, stock_num in init_stock.items(): used_num = used_dict[spec_code] if used_num >= stock_num: continue pack_v = spec_volume_map[spec_code] new_load = load_vol + pack_v if not allow_over and new_load > total_mat_vol: continue used_dict[spec_code] += 1 if dfs(new_load, used_dict): return True used_dict[spec_code] -= 1 return False start_used = {k:0 for k in init_stock.keys()} dfs(0.0, start_used) if best_solution is None: return PackResult( code=ResultCode.NO_ANY_LOAD_PLAN.value[0], msg="未找到可行包装组合方案", use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock=init_stock.copy(), total_use_package_count=0, search_state_count=visited_count, full_load_flag=False ) final_load_vol, final_used = best_solution remain_material = round(total_mat_vol - final_load_vol, 2) full_flag = remain_material <= 0.001 use_package_list = [] remain_stock = {} for spec_code, total_stock in init_stock.items(): use_cnt = final_used[spec_code] remain_cnt = total_stock - use_cnt remain_stock[spec_code] = remain_cnt if use_cnt > 0: use_package_list.append(SingleUsePackage( spec_code=spec_code, volume=spec_volume_map[spec_code], use_count=use_cnt )) total_use = sum([x.use_count for x in use_package_list]) return PackResult( code=ResultCode.SUCCESS.value[0], msg="配载方案计算成功", use_package_list=use_package_list, remain_material_volume=abs(remain_material), remain_package_stock=remain_stock, total_use_package_count=total_use, search_state_count=visited_count, full_load_flag=full_flag ) # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 21:27 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : strategy_astar.py """ A*启发式配载策略 """ import heapq from BreadthFirst.core.strategy_base import BasePackStrategy from BreadthFirst.core.models import PackQueryCondition, PackResult, SingleUsePackage from BreadthFirst.core.enums import ResultCode from BreadthFirst.config.app_config import AppConfig from BreadthFirst.core.logger import AppLogger logger = AppLogger.get_logger() class AStarPackStrategy(BasePackStrategy): """ """ def execute(self, condition: PackQueryCondition) -> PackResult: """ :param condition: :return: """ total_mat_vol = condition.total_material_volume spec_list = condition.package_spec_list allow_over = condition.allow_over_load max_state = AppConfig.MAX_SEARCH_STATE if total_mat_vol <= 0: return PackResult( code=ResultCode.PARAM_ERROR.value[0], msg="物料总体积必须大于0", use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock={}, total_use_package_count=0, search_state_count=0, full_load_flag=False ) if len(spec_list) == 0: return PackResult( code=ResultCode.NO_AVAILABLE_PACKAGE.value[0], msg=ResultCode.NO_AVAILABLE_PACKAGE.value[1], use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock={}, total_use_package_count=0, search_state_count=0, full_load_flag=False ) init_stock = {} spec_volume_map = {} for item in spec_list: init_stock[item.spec_code] = item.stock_qty spec_volume_map[item.spec_code] = item.volume visited = set() visited_count = 1 heap = [] start_used = {k:0 for k in init_stock.keys()} # f代价,g已使用包装数量,已装载体积,使用记录 heapq.heappush(heap, (0, 0, 0.0, start_used)) best_solution = None while heap: f, g, load_vol, used_dict = heapq.heappop(heap) state_key = (load_vol, tuple(used_dict.values())) if state_key in visited: continue visited.add(state_key) visited_count += 1 if load_vol >= total_mat_vol: best_solution = (load_vol, used_dict) break if visited_count >= max_state: logger.warning(f"A*搜索达到最大状态限制{max_state}") break for spec_code, stock_num in init_stock.items(): used_num = used_dict[spec_code] if used_num >= stock_num: continue pv = spec_volume_map[spec_code] new_load = load_vol + pv if not allow_over and new_load > total_mat_vol: continue new_g = g + 1 # 启发函数:剩余体积 h = max(0, total_mat_vol - new_load) new_f = new_g + h new_used = used_dict.copy() new_used[spec_code] += 1 heapq.heappush(heap, (new_f, new_g, new_load, new_used)) if best_solution is None: return PackResult( code=ResultCode.NO_ANY_LOAD_PLAN.value[0], msg="未找到可行包装组合方案", use_package_list=[], remain_material_volume=total_mat_vol, remain_package_stock=init_stock.copy(), total_use_package_count=0, search_state_count=visited_count, full_load_flag=False ) final_load_vol, final_used = best_solution remain_material = round(total_mat_vol - final_load_vol, 2) full_flag = remain_material <= 0.001 use_package_list = [] remain_stock = {} for spec_code, total_stock in init_stock.items(): use_cnt = final_used[spec_code] remain_cnt = total_stock - use_cnt remain_stock[spec_code] = remain_cnt if use_cnt > 0: use_package_list.append(SingleUsePackage( spec_code=spec_code, volume=spec_volume_map[spec_code], use_count=use_cnt )) total_use = sum([x.use_count for x in use_package_list]) return PackResult( code=ResultCode.SUCCESS.value[0], msg="配载方案计算成功", use_package_list=use_package_list, remain_material_volume=abs(remain_material), remain_package_stock=remain_stock, total_use_package_count=total_use, search_state_count=visited_count, full_load_flag=full_flag ) # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 21:28 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : thread_safe_pool.py """ 线程安全执行池,支持高并发调用配载算法 解决多线程竞争、资源复用 """ import concurrent.futures from threading import Lock from typing import List from BreadthFirst.core.models import PackQueryCondition, PackResult from BreadthFirst.core.strategy_base import BasePackStrategy from BreadthFirst.config.app_config import AppConfig from BreadthFirst.core.logger import AppLogger logger = AppLogger.get_logger() class SafePackThreadPool: """ """ _instance = None _lock = Lock() def __init__(self): self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=AppConfig.MAX_WORKER_THREAD) @classmethod def get_instance(cls): """ 单例线程池 :return: """ if cls._instance is None: with cls._lock: if cls._instance is None: cls._instance = SafePackThreadPool() return cls._instance def submit_task(self, strategy: BasePackStrategy, condition: PackQueryCondition) -> PackResult: """ 提交单次配载任务,同步等待结果 :param strategy: :param condition: :return: """ future = self.executor.submit(strategy.execute, condition) try: result = future.result(timeout=30) return result except Exception as e: logger.error(f"配载任务执行异常: {str(e)}") raise e def batch_submit(self, strategy: BasePackStrategy, condition_list: List[PackQueryCondition]) -> List[PackResult]: """ 批量并发执行多条配载请求 :param strategy: :param condition_list: :return: """ task_futures = [] for cond in condition_list: task_futures.append(self.executor.submit(strategy.execute, cond)) result_list = [] for ft in concurrent.futures.as_completed(task_futures): try: res = ft.result(timeout=30) result_list.append(res) except Exception as e: logger.error(f"批量任务异常 {str(e)}") return result_list def shutdown(self): """ 释放线程池,程序退出调用 :return: """ self.executor.shutdown(wait=True) # encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 21:29 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : package_service.py """ 业务服务层 对外统一入口,封装策略创建、参数组装、结果格式化 职责:解耦接口与底层算法 """ from typing import List from BreadthFirst.core.models import PackQueryCondition, PackResult, PackageSpec from BreadthFirst.core.enums import LoadStrategyType from BreadthFirst.core.strategy_base import BasePackStrategy from BreadthFirst.core.strategy_bfs import BFSPackStrategy from BreadthFirst.core.strategy_dfs import DFSPackStrategy from BreadthFirst.core.strategy_astar import AStarPackStrategy from BreadthFirst.concurrency.thread_safe_pool import SafePackThreadPool from BreadthFirst.core.logger import AppLogger logger = AppLogger.get_logger() class JewelryPackageService: """ 珠宝包装配载业务服务 """ def _create_strategy(self, strategy_type: str) -> BasePackStrategy: """ 根据策略标识实例化对应算法对象 :param strategy_type: :return: """ match strategy_type: case LoadStrategyType.BFS.value: return BFSPackStrategy() case LoadStrategyType.DFS.value: return DFSPackStrategy() case LoadStrategyType.ASTAR.value: return AStarPackStrategy() case _: raise ValueError(f"不支持的配载策略类型:{strategy_type}") def calc_single_load_plan(self, condition: PackQueryCondition) -> PackResult: """ 计算单个订单配载方案(同步) :param condition: 配载条件 :return: 配载结果 """ strategy = self._create_strategy(condition.strategy_type) thread_pool = SafePackThreadPool.get_instance() result = thread_pool.submit_task(strategy, condition) self._print_result_log(result) return result def batch_calc_load_plan(self, condition_list: List[PackQueryCondition]) -> List[PackResult]: """ 批量并发计算多个配载方案 :param condition_list: :return: """ if not condition_list: return [] strategy = self._create_strategy(condition_list[0].strategy_type) thread_pool = SafePackThreadPool.get_instance() result_list = thread_pool.batch_submit(strategy, condition_list) for res in result_list: self._print_result_log(res) return result_list def _print_result_log(self, result: PackResult): """ 格式化输出结果日志(控制台+日志文件统一打印) :param result: :return: """ logger.info("=====================配载结果=====================") logger.info(f"返回码:{result.code} 消息:{result.msg}") logger.info(f"搜索遍历状态总数:{result.search_state_count}") logger.info(f"总共使用包装数量:{result.total_use_package_count}") logger.info(f"物料是否完全装满:{result.full_load_flag}") logger.info(f"剩余未装入物料体积:{result.remain_material_volume:.2f} cm³") logger.info("本次选用包装清单:") for item in result.use_package_list: logger.info(f" 编码:{item.spec_code} 单盒容积:{item.volume}cm³ 使用数量:{item.use_count}") logger.info("包装库存剩余情况:") for code, num in result.remain_package_stock.items(): logger.info(f" {code} 剩余库存:{num}") logger.info("==================================================")调用:
# encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search # 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/26 21:30 # User : geovindu # Product : PyCharm # Project : PyAlgorithms # File : BreadthFirstBll.py """ 程序入口 main 提供控制台交互式菜单,模拟珠宝业务输入:物料体积、包装规格库存 """ from BreadthFirst.core.models import PackQueryCondition, PackageSpec from BreadthFirst.core.enums import LoadStrategyType from BreadthFirst.service.package_service import JewelryPackageService from BreadthFirst.core.logger import AppLogger class BreadthFirstBll(object): """ """ logger = AppLogger.get_logger() def input_float(self,msg: str) -> float: """ :param msg: :return: """ while True: try: val = float(input(msg)) if val <= 0: print("数值必须大于0!") continue return val except ValueError: print("输入格式错误,请输入数字!") def input_int(self,msg: str) -> int: """ :param msg: :return: """ while True: try: val = int(input(msg)) if val < 0: print("不能为负数!") continue return val except ValueError: print("输入格式错误,请输入整数!") def menu(self,): """ :return: """ service = JewelryPackageService() package_list: list[PackageSpec] = [] material_volume = 0.0 while True: print("\n================珠宝首饰包装智能配载系统================") print("1. 新建包装规格") print("2. 设置待装载首饰物料总体积(cm³)") print("3. 执行BFS最优配载计算(推荐,最少礼盒数量)") print("4. 执行DFS配载计算") print("5. 执行A*配载计算") print("0. 退出程序") opt = input("请输入选项:").strip() if opt == "1": while True: code = input("输入包装编码(例 BOX-500):").strip() vol = self.input_float("包装容积 cm³:") stock = self.input_int("当前可用库存数量:") package_list.append(PackageSpec(spec_code=code, volume=vol, stock_qty=stock)) again = input("继续新增包装?y/n:").strip().lower() if again != "y": break self.logger.info(f"已录入包装规格列表:{package_list}") elif opt == "2": material_volume = self.input_float("首饰+配件总容积 cm³:") self.logger.info(f"设置物料总体积:{material_volume} cm³") elif opt in ["3", "4", "5"]: if len(package_list) == 0 or material_volume <= 0: print("请先录入包装规格和物料体积!") continue strategy_map = { "3": LoadStrategyType.BFS.value, "4": LoadStrategyType.DFS.value, "5": LoadStrategyType.ASTAR.value } cond = PackQueryCondition( total_material_volume=material_volume, package_spec_list=package_list, strategy_type=strategy_map[opt], allow_over_load=False ) service.calc_single_load_plan(cond) elif opt == "0": self.logger.info("程序正常退出") break else: print("无效选项!") def Demo(self): """ :return: """ self.menu()输出: