go: Breadth First Search Algorithm and Depth First Search Algorithm

项目结构:

Go 复制代码
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:08
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : app_config.go
*/
package config
 
import "os"
 
// AppConfig 全局应用配置
type AppConfig struct {
    LogSavePath      string // 日志文件夹路径
    LogFileName      string // 日志文件名
    LogConsoleEnable bool
    LogFileEnable    bool
 
    MaxWorkerThread int // 协程池最大并发数量
    MaxSearchState  int // 搜索最大状态上限,防止爆炸
}
 
var GlobalConfig = &AppConfig{
    LogSavePath:      "./logs",
    LogFileName:      "jewelry_packer.log",
    LogConsoleEnable: true,
    LogFileEnable:    true,
    MaxWorkerThread:  8,
    MaxSearchState:   25000,
}
 
// InitLogDir 创建日志目录
func InitLogDir() error {
    if _, err := os.Stat(GlobalConfig.LogSavePath); os.IsNotExist(err) {
        return os.MkdirAll(GlobalConfig.LogSavePath, 0755)
    }
    return nil
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:09
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : enums.go
*/
package core
 
// LoadStrategyType 配载算法策略类型
type LoadStrategyType string
 
const (
    StrategyBFS   LoadStrategyType = "BFS"
    StrategyDFS   LoadStrategyType = "DFS"
    StrategyAStar LoadStrategyType = "ASTAR"
)
 
// ResultCode 返回业务码
type ResultCode struct {
    Code int
    Msg  string
}
 
var (
    CodeSuccess         = ResultCode{0, "成功"}
    CodeParamError      = ResultCode{400, "参数非法"}
    CodeNoAvailablePack = ResultCode{401, "无可用包装规格"}
    CodeConstraintFail  = ResultCode{402, "装箱约束冲突,无法装载"}
    CodeSearchOverLimit = ResultCode{501, "搜索状态超限,未找到方案"}
    CodeNoAnyLoadPlan   = ResultCode{502, "无法生成配载方案"}
)
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:09
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : models.go
*/
package core
 
// JewelryItem 单类首饰明细
type JewelryItem struct {
    JewelryCode  string  // 首饰货号
    SingleVolume float64 // 单件体积 cm³
    Quantity     int     // 品类数量
}
 
// GetTotalVolume 获取该品类首饰合计总体积
func (j *JewelryItem) GetTotalVolume() float64 {
    return j.SingleVolume * float64(j.Quantity)
}
 
// PackConstraint 装箱全局约束规则
type PackConstraint struct {
    BufferCoeff        float64 // 缓冲空隙系数,如0.85
    SingleBoxMaxQty    int     // 单个礼盒最多放置首饰件数
    ForbidMixJewelry   bool    // true=禁止不同品类混装
    ForbidSplitJewelry bool    // true=禁止同一品类拆分多个盒子
}
 
// PackageSpec 包装规格实体
type PackageSpec struct {
    SpecCode       string  // 包装编码
    Volume         float64 // 标称容积 cm³
    StockQty       int     // 可用库存
    UnitCost       float64 // 单个包装成本
    PriorityWeight int     // 优先级权重,数值越大优先选用
}
 
// GetValidVolume 根据缓冲系数计算有效装载容积
func (p *PackageSpec) GetValidVolume(coeff float64) float64 {
    return p.Volume * coeff
}
 
// PackQueryCondition 配载查询入参
type PackQueryCondition struct {
    JewelryList      []JewelryItem
    Constraint       *PackConstraint
    TotalMaterialVol float64
    PackageSpecList  []PackageSpec
    StrategyType     LoadStrategyType
    AllowOverLoad    bool
}
 
// SingleUsePackage 方案内使用的包装记录
type SingleUsePackage struct {
    SpecCode          string
    Volume            float64
    ValidVolume       float64
    UseCount          int
    BelongJewelryCode string
}
 
// PackResult 配载最终输出结果
type PackResult struct {
    Code                int
    Msg                 string
    UsePackageList      []SingleUsePackage
    RemainMaterialVol   float64
    RemainPackageStock  map[string]int
    TotalUsePackageCnt  int
    SearchStateCount    int
    FullLoadFlag        bool
    JewelryTotalCalcVol float64
    TotalCost           float64 // 方案总成本
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:10
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : logger.go
*/
package core
 
import (
    "github.com/rs/zerolog"
    "goalgorithms/breadthfirst/config"
    "io"
    "os"
    "sync"
)
 
var (
    loggerInstance *zerolog.Logger
    loggerOnce     sync.Once
)
 
// GetLogger 获取全局日志单例,协程安全
func GetLogger() *zerolog.Logger {
    loggerOnce.Do(func() {
        _ = config.InitLogDir()
        var writers []io.Writer
 
        // 控制台输出
        if config.GlobalConfig.LogConsoleEnable {
            consoleWriter := zerolog.ConsoleWriter{
                Out:        os.Stdout,
                TimeFormat: "2006-01-02 15:04:05",
            }
            writers = append(writers, consoleWriter)
        }
 
        // 文件输出
        if config.GlobalConfig.LogFileEnable {
            logPath := config.GlobalConfig.LogSavePath + "/" + config.GlobalConfig.LogFileName
            f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
            if err == nil {
                writers = append(writers, f)
            }
        }
 
        multi := io.MultiWriter(writers...)
        l := zerolog.New(multi).With().Timestamp().Logger()
        loggerInstance = &l
    })
    return loggerInstance
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:11
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : strategy_base.go
*/
package core
 
// PackStrategy 配载策略接口
type PackStrategy interface {
    Execute(condition *PackQueryCondition) *PackResult
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:11
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : strategy_bfs.go
*/
package core
 
import (
    "container/list"
    "fmt"
    "goalgorithms/breadthfirst/config"
    "math"
)
 
// BFSPackStrategy BFS广度优先搜索
// 择优优先级:最少包装盒数量 > 权重总和更高 > 总成本更低
type BFSPackStrategy struct{}
 
func NewBFSPackStrategy() PackStrategy {
    return &BFSPackStrategy{}
}
 
type bfsNode struct {
    loadVol   float64
    usedMap   map[string]int
    totalCost float64
    weightSum int
    packCount int
}
 
func (b *BFSPackStrategy) Execute(condition *PackQueryCondition) *PackResult {
    logger := GetLogger()
    totalMatVol := condition.TotalMaterialVol
    specList := condition.PackageSpecList
    allowOver := condition.AllowOverLoad
    maxState := config.GlobalConfig.MaxSearchState
 
    if totalMatVol <= 0 {
        return &PackResult{
            Code:               CodeParamError.Code,
            Msg:                CodeParamError.Msg,
            RemainMaterialVol:  totalMatVol,
            RemainPackageStock: map[string]int{},
        }
    }
    if len(specList) == 0 {
        return &PackResult{
            Code:               CodeNoAvailablePack.Code,
            Msg:                CodeNoAvailablePack.Msg,
            RemainMaterialVol:  totalMatVol,
            RemainPackageStock: map[string]int{},
        }
    }
 
    initStock := make(map[string]int)
    specVolMap := make(map[string]float64)
    specCostMap := make(map[string]float64)
    specWeightMap := make(map[string]int)
    for _, item := range specList {
        initStock[item.SpecCode] = item.StockQty
        specVolMap[item.SpecCode] = item.Volume
        specCostMap[item.SpecCode] = item.UnitCost
        specWeightMap[item.SpecCode] = item.PriorityWeight
    }
 
    // 初始化
    startUsed := make(map[string]int)
    for k := range initStock {
        startUsed[k] = 0
    }
    visited := make(map[string]bool)
    visitedCount := 1
    queue := list.New()
    startNode := bfsNode{
        loadVol:   0,
        usedMap:   copyMap(startUsed),
        totalCost: 0,
        weightSum: 0,
        packCount: 0,
    }
    queue.PushBack(startNode)
    visited[genStateKey(startUsed)] = true
 
    var bestNode *bfsNode
    var minPackCnt *int
 
    for queue.Len() > 0 {
        elem := queue.Front()
        queue.Remove(elem)
        node := elem.Value.(bfsNode)
 
        if node.loadVol >= totalMatVol {
            if bestNode == nil {
                bestNode = &node
                minPackCnt = &node.packCount
            } else {
                if node.packCount == *minPackCnt {
                    if node.weightSum > bestNode.weightSum ||
                        (node.weightSum == bestNode.weightSum && node.totalCost < bestNode.totalCost) {
                        bestNode = &node
                    }
                }
            }
            continue
        }
        if visitedCount >= maxState {
            logger.Warn().Int("maxState", maxState).Msg("搜索达到最大状态限制,终止搜索")
            break
        }
 
        for specCode, stockNum := range initStock {
            usedNum := node.usedMap[specCode]
            if usedNum >= stockNum {
                continue
            }
            packVol := specVolMap[specCode]
            newLoad := node.loadVol + packVol
            if !allowOver && newLoad > totalMatVol {
                if node.loadVol == 0 && packVol >= totalMatVol {
                } else {
                    continue
                }
            }
            newUsed := copyMap(node.usedMap)
            newUsed[specCode]++
            key := genStateKey(newUsed)
            if visited[key] {
                continue
            }
            visited[key] = true
            visitedCount++
            newNode := bfsNode{
                loadVol:   newLoad,
                usedMap:   newUsed,
                totalCost: node.totalCost + specCostMap[specCode],
                weightSum: node.weightSum + specWeightMap[specCode],
                packCount: node.packCount + 1,
            }
            queue.PushBack(newNode)
        }
    }
 
    if bestNode == nil {
        return &PackResult{
            Code:               CodeNoAnyLoadPlan.Code,
            Msg:                CodeNoAnyLoadPlan.Msg,
            RemainMaterialVol:  totalMatVol,
            RemainPackageStock: initStock,
            SearchStateCount:   visitedCount,
        }
    }
 
    remainMat := math.Max(0, totalMatVol-bestNode.loadVol)
    fullFlag := bestNode.loadVol >= totalMatVol
    useList := make([]SingleUsePackage, 0)
    remainStock := make(map[string]int)
    jewelryCode := ""
    if len(condition.JewelryList) > 0 {
        jewelryCode = condition.JewelryList[0].JewelryCode
    }
    for code, totalStock := range initStock {
        useCnt := bestNode.usedMap[code]
        remainStock[code] = totalStock - useCnt
        if useCnt > 0 {
            useList = append(useList, SingleUsePackage{
                SpecCode:          code,
                Volume:            specVolMap[code],
                ValidVolume:       specVolMap[code],
                UseCount:          useCnt,
                BelongJewelryCode: jewelryCode,
            })
        }
    }
 
    return &PackResult{
        Code:               CodeSuccess.Code,
        Msg:                CodeSuccess.Msg,
        UsePackageList:     useList,
        RemainMaterialVol:  remainMat,
        RemainPackageStock: remainStock,
        TotalUsePackageCnt: bestNode.packCount,
        SearchStateCount:   visitedCount,
        FullLoadFlag:       fullFlag,
        TotalCost:          bestNode.totalCost,
    }
}
 
func copyMap(m map[string]int) map[string]int {
    n := make(map[string]int, len(m))
    for k, v := range m {
        n[k] = v
    }
    return n
}
 
func genStateKey(m map[string]int) string {
    var s string
    for k, v := range m {
        s += fmt.Sprintf("%s:%d,", k, v)
    }
    return s
}
 
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:12
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : strategy_dfs.go
*/
package core
 
import (
    "goalgorithms/breadthfirst/config"
    "math"
)
 
type DFSPackStrategy struct {
    bestNode    *dfsNode
    visited     map[string]bool
    visitedCnt  int
    maxState    int
    totalMatVol float64
    allowOver   bool
    specVolMap  map[string]float64
    specCostMap map[string]float64
    initStock   map[string]int
}
 
type dfsNode struct {
    loadVol   float64
    usedMap   map[string]int
    totalCost float64
    packCnt   int
}
 
func NewDFSPackStrategy() PackStrategy {
    return &DFSPackStrategy{}
}
 
func (d *DFSPackStrategy) Execute(condition *PackQueryCondition) *PackResult {
    d.totalMatVol = condition.TotalMaterialVol
    d.allowOver = condition.AllowOverLoad
    d.maxState = config.GlobalConfig.MaxSearchState
    specList := condition.PackageSpecList
 
    if d.totalMatVol <= 0 {
        return &PackResult{Code: CodeParamError.Code, Msg: CodeParamError.Msg}
    }
    if len(specList) == 0 {
        return &PackResult{Code: CodeNoAvailablePack.Code, Msg: CodeNoAvailablePack.Msg}
    }
 
    d.initStock = make(map[string]int)
    d.specVolMap = make(map[string]float64)
    d.specCostMap = make(map[string]float64)
    for _, item := range specList {
        d.initStock[item.SpecCode] = item.StockQty
        d.specVolMap[item.SpecCode] = item.Volume
        d.specCostMap[item.SpecCode] = item.UnitCost
    }
 
    startUsed := make(map[string]int)
    for k := range d.initStock {
        startUsed[k] = 0
    }
    d.visited = make(map[string]bool)
    d.visitedCnt = 0
    d.bestNode = nil
 
    d.dfs(0, startUsed, 0, 0)
 
    if d.bestNode == nil {
        return &PackResult{
            Code:               CodeNoAnyLoadPlan.Code,
            Msg:                CodeNoAnyLoadPlan.Msg,
            RemainMaterialVol:  d.totalMatVol,
            RemainPackageStock: copyMap(d.initStock),
            SearchStateCount:   d.visitedCnt,
        }
    }
 
    remainMat := math.Max(0, d.totalMatVol-d.bestNode.loadVol)
    fullFlag := d.bestNode.loadVol >= d.totalMatVol
    useList := make([]SingleUsePackage, 0)
    remainStock := make(map[string]int)
    jewelryCode := ""
    if len(condition.JewelryList) > 0 {
        jewelryCode = condition.JewelryList[0].JewelryCode
    }
    for code, totalStock := range d.initStock {
        useCnt := d.bestNode.usedMap[code]
        remainStock[code] = totalStock - useCnt
        if useCnt > 0 {
            useList = append(useList, SingleUsePackage{
                SpecCode:          code,
                Volume:            d.specVolMap[code],
                ValidVolume:       d.specVolMap[code],
                UseCount:          useCnt,
                BelongJewelryCode: jewelryCode,
            })
        }
    }
 
    return &PackResult{
        Code:               CodeSuccess.Code,
        Msg:                CodeSuccess.Msg,
        UsePackageList:     useList,
        RemainMaterialVol:  remainMat,
        RemainPackageStock: remainStock,
        TotalUsePackageCnt: d.bestNode.packCnt,
        SearchStateCount:   d.visitedCnt,
        FullLoadFlag:       fullFlag,
        TotalCost:          d.bestNode.totalCost,
    }
}
 
func (d *DFSPackStrategy) dfs(loadVol float64, usedMap map[string]int, cost float64, packCnt int) {
    if d.bestNode != nil {
        return
    }
    if loadVol >= d.totalMatVol {
        d.bestNode = &dfsNode{
            loadVol:   loadVol,
            usedMap:   copyMap(usedMap),
            totalCost: cost,
            packCnt:   packCnt,
        }
        return
    }
    key := genStateKey(usedMap)
    if d.visited[key] {
        return
    }
    if d.visitedCnt >= d.maxState {
        return
    }
    d.visited[key] = true
    d.visitedCnt++
 
    for specCode, stockNum := range d.initStock {
        usedNum := usedMap[specCode]
        if usedNum >= stockNum {
            continue
        }
        pv := d.specVolMap[specCode]
        newLoad := loadVol + pv
        if !d.allowOver && newLoad > d.totalMatVol {
            if loadVol == 0 && pv >= d.totalMatVol {
            } else {
                continue
            }
        }
        usedMap[specCode]++
        d.dfs(newLoad, usedMap, cost+d.specCostMap[specCode], packCnt+1)
        usedMap[specCode]--
    }
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:13
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : strategy_astar.go
*/
package core
 
import (
    "container/heap"
    "goalgorithms/breadthfirst/config"
    "math"
)
 
type AStarPackStrategy struct{}
 
func NewAStarPackStrategy() PackStrategy {
    return &AStarPackStrategy{}
}
 
type astarItem struct {
    f       float64
    g       int
    loadVol float64
    cost    float64
    usedMap map[string]int
}
type priorityQueue []*astarItem
 
func (pq priorityQueue) Len() int            { return len(pq) }
func (pq priorityQueue) Less(i, j int) bool  { return pq[i].f < pq[j].f }
func (pq priorityQueue) Swap(i, j int)       { pq[i], pq[j] = pq[j], pq[i] }
func (pq *priorityQueue) Push(x interface{}) { *pq = append(*pq, x.(*astarItem)) }
func (pq *priorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    *pq = old[:n-1]
    return item
}
 
func (a *AStarPackStrategy) Execute(condition *PackQueryCondition) *PackResult {
    logger := GetLogger()
    totalMatVol := condition.TotalMaterialVol
    specList := condition.PackageSpecList
    allowOver := condition.AllowOverLoad
    maxState := config.GlobalConfig.MaxSearchState
 
    if totalMatVol <= 0 {
        return &PackResult{Code: CodeParamError.Code, Msg: CodeParamError.Msg}
    }
    if len(specList) == 0 {
        return &PackResult{Code: CodeNoAvailablePack.Code, Msg: CodeNoAvailablePack.Msg}
    }
 
    initStock := make(map[string]int)
    specVolMap := make(map[string]float64)
    specCostMap := make(map[string]float64)
    specWeightMap := make(map[string]int)
    for _, item := range specList {
        initStock[item.SpecCode] = item.StockQty
        specVolMap[item.SpecCode] = item.Volume
        specCostMap[item.SpecCode] = item.UnitCost
        specWeightMap[item.SpecCode] = item.PriorityWeight
    }
 
    startUsed := make(map[string]int)
    for k := range initStock {
        startUsed[k] = 0
    }
    visited := make(map[string]bool)
    visitedCount := 1
    pq := make(priorityQueue, 0)
    heap.Init(&pq)
    heap.Push(&pq, &astarItem{f: 0, g: 0, loadVol: 0, cost: 0, usedMap: copyMap(startUsed)})
 
    var best *astarItem
 
    for pq.Len() > 0 {
        top := heap.Pop(&pq).(*astarItem)
        key := genStateKey(top.usedMap)
        if visited[key] {
            continue
        }
        visited[key] = true
        visitedCount++
 
        if top.loadVol >= totalMatVol {
            best = top
            break
        }
        if visitedCount >= maxState {
            logger.Warn().Msg("A*搜索达到最大状态限制")
            break
        }
 
        for specCode, stockNum := range initStock {
            usedNum := top.usedMap[specCode]
            if usedNum >= stockNum {
                continue
            }
            pv := specVolMap[specCode]
            newLoad := top.loadVol + pv
            if !allowOver && newLoad > totalMatVol {
                if top.loadVol == 0 && pv >= totalMatVol {
                } else {
                    continue
                }
            }
            newG := top.g + 1
            newCost := top.cost + specCostMap[specCode]
            h := maxFloat(totalMatVol-newLoad, 0)*0.01 + newCost*0.15 - float64(specWeightMap[specCode])*0.005
            newF := float64(newG) + h
            newUsed := copyMap(top.usedMap)
            newUsed[specCode]++
            heap.Push(&pq, &astarItem{
                f:       newF,
                g:       newG,
                loadVol: newLoad,
                cost:    newCost,
                usedMap: newUsed,
            })
        }
    }
 
    if best == nil {
        return &PackResult{
            Code:               CodeNoAnyLoadPlan.Code,
            Msg:                CodeNoAnyLoadPlan.Msg,
            RemainMaterialVol:  totalMatVol,
            RemainPackageStock: copyMap(initStock),
            SearchStateCount:   visitedCount,
        }
    }
 
    remainMat := math.Max(0, totalMatVol-best.loadVol)
    fullFlag := best.loadVol >= totalMatVol
    useList := make([]SingleUsePackage, 0)
    remainStock := make(map[string]int)
    jewelryCode := ""
    if len(condition.JewelryList) > 0 {
        jewelryCode = condition.JewelryList[0].JewelryCode
    }
    for code, totalStock := range initStock {
        useCnt := best.usedMap[code]
        remainStock[code] = totalStock - useCnt
        if useCnt > 0 {
            useList = append(useList, SingleUsePackage{
                SpecCode:          code,
                Volume:            specVolMap[code],
                ValidVolume:       specVolMap[code],
                UseCount:          useCnt,
                BelongJewelryCode: jewelryCode,
            })
        }
    }
 
    return &PackResult{
        Code:               CodeSuccess.Code,
        Msg:                CodeSuccess.Msg,
        UsePackageList:     useList,
        RemainMaterialVol:  remainMat,
        RemainPackageStock: remainStock,
        TotalUsePackageCnt: best.g,
        SearchStateCount:   visitedCount,
        FullLoadFlag:       fullFlag,
        TotalCost:          best.cost,
    }
}
 
func maxFloat(a, b float64) float64 {
    if a > b {
        return a
    }
    return b
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:15
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : thread_pool.go
*/
package concurrency
 
import (
    "goalgorithms/breadthfirst/config"
    "goalgorithms/breadthfirst/core"
    "sync"
    "time"
)
 
type taskFunc func()
 
// SafePool 协程池单例
type SafePool struct {
    wg    sync.WaitGroup
    taskC chan taskFunc
}
 
var poolOnce sync.Once
var globalPool *SafePool
 
func GetPool() *SafePool {
    poolOnce.Do(func() {
        maxWorker := config.GlobalConfig.MaxWorkerThread
        p := &SafePool{
            taskC: make(chan taskFunc, maxWorker*2),
        }
        for i := 0; i < maxWorker; i++ {
            go func() {
                for fn := range p.taskC {
                    fn()
                }
            }()
        }
        globalPool = p
    })
    return globalPool
}
 
// Submit 提交单个任务同步等待
func (p *SafePool) Submit(strategy core.PackStrategy, cond *core.PackQueryCondition) *core.PackResult {
    resChan := make(chan *core.PackResult, 1)
    p.taskC <- func() {
        resChan <- strategy.Execute(cond)
    }
    select {
    case r := <-resChan:
        return r
    case <-time.After(30 * time.Second):
        return &core.PackResult{
            Code: core.CodeSearchOverLimit.Code,
            Msg:  "任务执行超时",
        }
    }
}
 
 
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:15
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : package_service.go
*/
package service
 
import (
    "fmt"
    "strings"
 
    "github.com/rs/zerolog"
    "goalgorithms/breadthfirst/concurrency"
    "goalgorithms/breadthfirst/core"
)
 
type JewelryPackageService struct {
    logger *zerolog.Logger
}
 
func NewService() *JewelryPackageService {
    return &JewelryPackageService{
        logger: core.GetLogger(),
    }
}
 
// CalcJewelryTotalVolume 汇总所有首饰总体积
func (s *JewelryPackageService) CalcJewelryTotalVolume(list []core.JewelryItem) float64 {
    var total float64
    s.logger.Info().Msg("=====开始计算各类首饰物料体积明细=====")
    for _, item := range list {
        vol := item.GetTotalVolume()
        s.logger.Info().
            Str("货号", item.JewelryCode).
            Float64("单件体积", item.SingleVolume).
            Int("数量", item.Quantity).
            Float64("品类合计", vol).Msg("")
        total += vol
    }
    s.logger.Info().Float64("理论总体积", total).Msg("【汇总完成】")
    return total
}
 
// CheckForbidSplitConstraint 禁止拆盒前置校验
func (s *JewelryPackageService) CheckForbidSplitConstraint(item *core.JewelryItem, packages []core.PackageSpec, coeff float64, maxQty int) bool {
    if maxQty > 0 && item.Quantity > maxQty {
        s.logger.Warn().
            Str("jewelryCode", item.JewelryCode).
            Int("qty", item.Quantity).
            Int("maxQty", maxQty).
            Msg("约束调整:单盒最大件数限制小于品类数量,自动允许拆分该品类")
        return true
    }
    totalV := item.GetTotalVolume()
    for _, pkg := range packages {
        valid := pkg.GetValidVolume(coeff)
        if totalV <= valid {
            return true
        }
    }
    s.logger.Error().
        Str("jewelryCode", item.JewelryCode).
        Float64("needVol", totalV).
        Msg("约束冲突:禁止拆盒,但无盒子完整容纳该品类")
    return false
}
 
// SplitByJewelry 禁止混装时拆分多个独立任务
func (s *JewelryPackageService) SplitByJewelry(origin *core.PackQueryCondition) []*core.PackQueryCondition {
    cons := origin.Constraint
    if cons == nil || !cons.ForbidMixJewelry {
        return []*core.PackQueryCondition{origin}
    }
    s.logger.Info().Msg("【启用禁止混装规则】各品类独立配载")
    var subList []*core.PackQueryCondition
    for _, j := range origin.JewelryList {
        if cons.ForbidSplitJewelry && cons.SingleBoxMaxQty > 0 && j.Quantity > cons.SingleBoxMaxQty {
            s.logger.Warn().
                Str("jewelryCode", j.JewelryCode).
                Int("qty", j.Quantity).
                Int("maxQty", cons.SingleBoxMaxQty).
                Msg("自动拆分品类:禁止拆盒与单盒最大件数冲突,按最大件数拆分")
            numGroups := (j.Quantity + cons.SingleBoxMaxQty - 1) / cons.SingleBoxMaxQty
            for i := 0; i < numGroups; i++ {
                start := i * cons.SingleBoxMaxQty
                end := start + cons.SingleBoxMaxQty
                if end > j.Quantity {
                    end = j.Quantity
                }
                subQty := end - start
                subItem := core.JewelryItem{
                    JewelryCode:   j.JewelryCode + fmt.Sprintf("_%d", i+1),
                    SingleVolume:  j.SingleVolume,
                    Quantity:      subQty,
                }
                subVol := subItem.GetTotalVolume()
                subCond := &core.PackQueryCondition{
                    JewelryList:      []core.JewelryItem{subItem},
                    Constraint:       cons,
                    TotalMaterialVol: subVol,
                    PackageSpecList:  origin.PackageSpecList,
                    StrategyType:     origin.StrategyType,
                    AllowOverLoad:    origin.AllowOverLoad,
                }
                subList = append(subList, subCond)
            }
            continue
        }
        if cons.ForbidSplitJewelry {
            if !s.CheckForbidSplitConstraint(&j, origin.PackageSpecList, cons.BufferCoeff, cons.SingleBoxMaxQty) {
                continue
            }
        }
        subItem := j
        subVol := j.GetTotalVolume()
        subCond := &core.PackQueryCondition{
            JewelryList:      []core.JewelryItem{subItem},
            Constraint:       cons,
            TotalMaterialVol: subVol,
            PackageSpecList:  origin.PackageSpecList,
            StrategyType:     origin.StrategyType,
            AllowOverLoad:    origin.AllowOverLoad,
        }
        subList = append(subList, subCond)
    }
    return subList
}
 
func (s *JewelryPackageService) CreateStrategy(t core.LoadStrategyType) core.PackStrategy {
    switch t {
    case core.StrategyBFS:
        return core.NewBFSPackStrategy()
    case core.StrategyDFS:
        return core.NewDFSPackStrategy()
    case core.StrategyAStar:
        return core.NewAStarPackStrategy()
    default:
        panic(fmt.Sprintf("不支持策略类型 %s", t))
    }
}
 
func (s *JewelryPackageService) CalcSinglePlan(cond *core.PackQueryCondition) *core.PackResult {
    if cond.Constraint != nil {
        c := cond.Constraint
        s.logger.Info().Msg("=======装箱约束参数=======")
        s.logger.Info().Float64("缓冲系数", c.BufferCoeff).Msg("")
        s.logger.Info().Int("单盒最大件数", c.SingleBoxMaxQty).Msg("")
        s.logger.Info().Bool("禁止混装", c.ForbidMixJewelry).Msg("")
        s.logger.Info().Bool("禁止拆盒", c.ForbidSplitJewelry).Msg("")
    }
    calcVol := 0.0
    if len(cond.JewelryList) > 0 {
        calcVol = s.CalcJewelryTotalVolume(cond.JewelryList)
        cond.TotalMaterialVol = calcVol
    }
 
    subConditions := s.SplitByJewelry(cond)
    if len(subConditions) == 0 {
        s.logger.Error().Msg("所有品类均无法满足约束条件,无法进行配载")
        remainStock := make(map[string]int)
        for _, pkg := range cond.PackageSpecList {
            remainStock[pkg.SpecCode] = pkg.StockQty
        }
        merge := &core.PackResult{
            Code:               core.CodeNoAnyLoadPlan.Code,
            Msg:                "所有品类均无法满足约束条件",
            UsePackageList:     []core.SingleUsePackage{},
            RemainMaterialVol:  calcVol,
            RemainPackageStock: remainStock,
            TotalUsePackageCnt: 0,
            SearchStateCount:   0,
            FullLoadFlag:       false,
            TotalCost:          0,
            JewelryTotalCalcVol: calcVol,
        }
        s.PrintResultLog(merge)
        return merge
    }
    strategy := s.CreateStrategy(cond.StrategyType)
    pool := concurrency.GetPool()
    var subResults []*core.PackResult
    for _, sc := range subConditions {
        subRes := pool.Submit(strategy, sc)
        subResults = append(subResults, subRes)
    }
    merge := s.MergeSubResult(subResults, cond)
    merge.JewelryTotalCalcVol = calcVol
    s.PrintResultLog(merge)
    return merge
}
 
// MergeSubResult 合并多个品类独立配载结果
func (s *JewelryPackageService) MergeSubResult(subList []*core.PackResult, origin *core.PackQueryCondition) *core.PackResult {
    var usePackages []core.SingleUsePackage
    remainStock := make(map[string]int)
    remainMatSum := 0.0
    totalSearch := 0
    totalCostSum := 0.0
    allSuccess := true
    failedCount := 0
    successCount := 0
 
    for _, pkg := range origin.PackageSpecList {
        remainStock[pkg.SpecCode] = pkg.StockQty
    }
    for _, sub := range subList {
        if sub.Code != core.CodeSuccess.Code {
            allSuccess = false
            failedCount++
        } else {
            successCount++
        }
        remainMatSum += sub.RemainMaterialVol
        totalSearch += sub.SearchStateCount
        totalCostSum += sub.TotalCost
        usePackages = append(usePackages, sub.UsePackageList...)
        for _, pu := range sub.UsePackageList {
            remainStock[pu.SpecCode] -= pu.UseCount
        }
    }
    totalPackCnt := 0
    for _, pu := range usePackages {
        totalPackCnt += pu.UseCount
    }
    fullFlag := remainMatSum <= 0.01
    if !allSuccess {
        msg := fmt.Sprintf("部分品类无法找到可行包装方案 (成功: %d, 失败: %d)", successCount, failedCount)
        return &core.PackResult{
            Code:               core.CodeNoAnyLoadPlan.Code,
            Msg:                msg,
            UsePackageList:     usePackages,
            RemainMaterialVol:  remainMatSum,
            RemainPackageStock: remainStock,
            TotalUsePackageCnt: totalPackCnt,
            SearchStateCount:   totalSearch,
            FullLoadFlag:       fullFlag,
            TotalCost:          totalCostSum,
        }
    }
    return &core.PackResult{
        Code:               core.CodeSuccess.Code,
        Msg:                core.CodeSuccess.Msg,
        UsePackageList:     usePackages,
        RemainMaterialVol:  remainMatSum,
        RemainPackageStock: remainStock,
        TotalUsePackageCnt: totalPackCnt,
        SearchStateCount:   totalSearch,
        FullLoadFlag:       fullFlag,
        TotalCost:          totalCostSum,
    }
}
 
func (s *JewelryPackageService) PrintResultLog(res *core.PackResult) {
    fmt.Println("=====================配载结果=====================")
    fmt.Printf("首饰理论总体积: %.2f cm³\n", res.JewelryTotalCalcVol)
    fmt.Printf("返回码: %d, 消息: %s\n", res.Code, res.Msg)
    fmt.Printf("搜索状态总数: %d\n", res.SearchStateCount)
    fmt.Printf("总共包装盒数量: %d\n", res.TotalUsePackageCnt)
    fmt.Printf("方案总成本: %.2f\n", res.TotalCost)
    fmt.Printf("全部装载完成: %t\n", res.FullLoadFlag)
    fmt.Printf("剩余物料体积: %.2f cm³\n", res.RemainMaterialVol)
     
    fmt.Println("\n【品类配载详情】")
    jewelryGroups := make(map[string]map[string]int)
    jewelryVolMap := make(map[string]float64)
    jewelryTotalUsed := make(map[string]int)
    for _, item := range res.UsePackageList {
        jewelryCode := item.BelongJewelryCode
        if idx := strings.Index(jewelryCode, "_"); idx > 0 {
            jewelryCode = jewelryCode[:idx]
        }
        if _, ok := jewelryGroups[jewelryCode]; !ok {
            jewelryGroups[jewelryCode] = make(map[string]int)
        }
        jewelryGroups[jewelryCode][item.SpecCode] += item.UseCount
        jewelryVolMap[item.SpecCode] = item.Volume
        jewelryTotalUsed[jewelryCode] += item.UseCount
    }
    if len(jewelryGroups) == 0 {
        fmt.Println("  无")
    } else {
        for jewelryCode, specMap := range jewelryGroups {
            fmt.Printf("\n  货号: %s\n", jewelryCode)
            fmt.Printf("    已装数量: %d 盒\n", jewelryTotalUsed[jewelryCode])
            for specCode, count := range specMap {
                fmt.Printf("    └─ 规格: %s, 有效容积: %.2f cm³, 使用数量: %d\n",
                    specCode, jewelryVolMap[specCode], count)
            }
        }
    }
     
    fmt.Println("\n【包装规格汇总】")
    specGroups := make(map[string]int)
    specVolMap := make(map[string]float64)
    for _, item := range res.UsePackageList {
        specGroups[item.SpecCode] += item.UseCount
        specVolMap[item.SpecCode] = item.Volume
    }
    if len(specGroups) == 0 {
        fmt.Println("  无")
    } else {
        for specCode, count := range specGroups {
            fmt.Printf("  规格: %s, 有效容积: %.2f cm³, 使用数量: %d\n",
                specCode, specVolMap[specCode], count)
        }
    }
     
    fmt.Println("\n【包装库存剩余】")
    if len(res.RemainPackageStock) == 0 {
        fmt.Println("  无")
    } else {
        for code, num := range res.RemainPackageStock {
            fmt.Printf("  规格: %s, 剩余数量: %d\n", code, num)
        }
    }
    fmt.Println("\n==================================================")
}

调用:

Go 复制代码
/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/26 22:16
# User      :  geovindu
# Product   : GoLand
# Project   : goalgorithms
# File      : breadthfirstbll.go
*/
package bll
 
import (
    "bufio"
    "fmt"
    "goalgorithms/breadthfirst/core"
    "goalgorithms/breadthfirst/service"
    "os"
    "strconv"
    "strings"
)
 
var scanner = bufio.NewScanner(os.Stdin)
 
func readString(prompt string) string {
    fmt.Print(prompt)
    scanner.Scan()
    return strings.TrimSpace(scanner.Text())
}
func readFloat(prompt string) float64 {
    for {
        s := readString(prompt)
        f, err := strconv.ParseFloat(s, 64)
        if err == nil && f > 0 {
            return f
        }
        fmt.Println("输入错误,请输入大于0数字")
    }
}
func readInt(prompt string) int {
    for {
        s := readString(prompt)
        i, err := strconv.Atoi(s)
        if err == nil && i >= 0 {
            return i
        }
        fmt.Println("输入错误,请输入非负整数")
    }
}
func readBool(prompt string) bool {
    for {
        s := readString(prompt + " y/n:")
        if s == "y" {
            return true
        } else if s == "n" {
            return false
        }
        fmt.Println("只能输入 y / n")
    }
}
 
func BreadthFirstMain() {
    svc := service.NewService()
    var jewelryList []core.JewelryItem
    var pkgList []core.PackageSpec
    var constraint *core.PackConstraint
 
    for {
        fmt.Println("\n================珠宝首饰智能包装配载系统 Go版================")
        fmt.Println("1. 录入首饰品类清单")
        fmt.Println("2. 录入包装规格(容积、成本、优先级权重、库存)")
        fmt.Println("3. 设置装箱约束")
        fmt.Println("4. BFS配载(推荐)")
        fmt.Println("5. DFS配载")
        fmt.Println("6. A*启发配载")
        fmt.Println("0. 退出")
        opt := readString("请输入选项:")
        switch opt {
        case "1":
            jewelryList = nil
            for {
                code := readString("首饰货号:")
                sv := readFloat("单件首饰体积 cm³:")
                qty := readInt("品类数量:")
                jewelryList = append(jewelryList, core.JewelryItem{
                    JewelryCode:  code,
                    SingleVolume: sv,
                    Quantity:     qty,
                })
                again := readBool("继续录入首饰?")
                if !again {
                    break
                }
            }
        case "2":
            pkgList = nil
            for {
                code := readString("包装编码:")
                vol := readFloat("标称容积 cm³:")
                cost := readFloat("单个包装成本:")
                weight := readInt("优先级权重(1~100):")
                stock := readInt("可用库存:")
                pkgList = append(pkgList, core.PackageSpec{
                    SpecCode:       code,
                    Volume:         vol,
                    UnitCost:       cost,
                    PriorityWeight: weight,
                    StockQty:       stock,
                })
                again := readBool("继续录入包装?")
                if !again {
                    break
                }
            }
        case "3":
            bufCoeff := readFloat("缓冲空隙系数(0.7~0.95):")
            maxBoxQty := readInt("单个礼盒最大首饰件数:")
            forbidMix := readBool("禁止不同品类混装?")
            forbidSplit := readBool("禁止同一品类拆分多个盒子?")
            constraint = &core.PackConstraint{
                BufferCoeff:        bufCoeff,
                SingleBoxMaxQty:    maxBoxQty,
                ForbidMixJewelry:   forbidMix,
                ForbidSplitJewelry: forbidSplit,
            }
        case "4", "5", "6":
            if len(jewelryList) == 0 || len(pkgList) == 0 || constraint == nil {
                fmt.Println("请先录入首饰、包装、装箱约束!")
                continue
            }
            var st core.LoadStrategyType
            switch opt {
            case "4":
                st = core.StrategyBFS
            case "5":
                st = core.StrategyDFS
            case "6":
                st = core.StrategyAStar
            }
            cond := &core.PackQueryCondition{
                JewelryList:     jewelryList,
                Constraint:      constraint,
                PackageSpecList: pkgList,
                StrategyType:    st,
                AllowOverLoad:   false,
            }
            _ = svc.CalcSinglePlan(cond)
        case "0":
            fmt.Println("程序退出")
            return
        default:
            fmt.Println("无效选项")
        }
    }
}

输出:

相关推荐
国科安芯1 小时前
FreeRTOS RISC-V 浮点上下文切换移植:在 IAR 工程中完整保存 FPU 寄存器
java·开发语言·单片机·嵌入式硬件·算法·系统架构·risc-v
Conan在掘金1 小时前
ArkTS 进阶之道(13):ForEach 循环渲染边界——为啥 build 里不能写 for 循环
后端
小poop2 小时前
轮转数组:从暴力到最优,一题掌握算法复杂度分析
数据结构·算法·leetcode
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(四十一):增加管理员账号管理接口
后端·go·gin
用户0678260743272 小时前
APP版本管理全链路(后端设计)
后端
樱桃读报僵尸2 小时前
说说 LLMRouter,Agent 执行过程中怎么动态的选择 LLM
算法
suconnect2 小时前
Spring Boot接入企业RAG:文档切分、向量检索、权限过滤和答案溯源
java·spring boot·后端
神奇小汤圆2 小时前
别再用 nohup java -jar 了:Spring Boot 生产环境该怎么守护?
后端
JavaGuide2 小时前
GitHub 9.8 万 Star!把整个代码仓库变成知识图谱,这个 AI Coding 工具太适合 Claude Code / Codex 了
前端·后端·ai编程
旺仔学长 哈哈3 小时前
Spring Boot 智能停车场管理系统---附源码+数据库文档
数据库·spring boot·后端·智能停车场