python: Breadth First Search Algorithm and Depth First Search Algorithm

项目结构:

python 复制代码
# 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("==================================================")

调用:

python 复制代码
# 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()

输出:

相关推荐
程序员果子1 小时前
CrewAI :当 Agent 学会团队协作
人工智能·git·python·多智能体·agent框架
玉鸯1 小时前
向量检索不是记忆:Agent 记忆的三层进化
python·agent
大模型码小白1 小时前
Java 部署:Jenkins Pipeline 构建 Java 项目(自动化)
java·人工智能·python·机器学习
长不胖的路人甲1 小时前
可达性分析法(根搜索算法)完整详解
java·jvm·算法
paeamecium1 小时前
【PAT甲级真题】- Kuchiguse (20)
数据结构·c++·python·算法·pat考试·pat
程序员清风1 小时前
AI不是万能的,大家要专注实践!
java·后端·面试
青山木1 小时前
Hot 100 --- 全排列
java·数据结构·算法·leetcode·深度优先
海兰2 小时前
【高速缓存】RedisVL 存储类型选择指南:Hash 与 JSON
人工智能·redis·算法·缓存·json·哈希算法
KaMeidebaby2 小时前
卡梅德生物技术快报|核酸适配体文库筛选:核酸适配体文库筛选全流程技术解析:NGS与AI辅助方案的设计与实践
前端·人工智能·物联网·算法·百度