项目结构:

cs
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : AppConfig.cs
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Config
{
/// <summary>
/// 全局应用配置
/// </summary>
public static class AppConfig
{
public static string LogSavePath = "./logs";
public static string LogFileName = "jewelry_packer.log";
public static bool LogConsoleEnable = true;
public static bool LogFileEnable = true;
public static int MaxWorkerThread = 8;
public static int MaxSearchState = 25000;
public static void InitLogDir()
{
if (!Directory.Exists(LogSavePath))
{
Directory.CreateDirectory(LogSavePath);
}
}
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : Enums.cs
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
/// <summary>
/// 配载算法策略
/// </summary>
public enum LoadStrategyType
{
BFS,
DFS,
ASTAR
}
/// <summary>
/// 返回码实体
/// </summary>
public record ResultCode(int Code, string Msg)
{
public static readonly ResultCode Success = new(0, "成功");
public static readonly ResultCode ParamError = new(400, "参数非法");
public static readonly ResultCode NoAvailablePackage = new(401, "无可用包装规格");
public static readonly ResultCode ConstraintFail = new(402, "装箱约束冲突,无法装载");
public static readonly ResultCode SearchOverLimit = new(501, "搜索状态超限,未找到方案");
public static readonly ResultCode NoAnyLoadPlan = new(502, "无法生成配载方案");
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : Models.cs
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
/// <summary>
/// 单类首饰明细
/// </summary>
public class JewelryItem
{
public string JewelryCode { get; set; } = string.Empty;
public double SingleVolume { get; set; }
public int Quantity { get; set; }
public double GetTotalVolume()
{
return SingleVolume * Quantity;
}
}
/// <summary>
/// 装箱全局约束
/// </summary>
public class PackConstraint
{
public double BufferCoeff { get; set; }
public int SingleBoxMaxQty { get; set; }
public bool ForbidMixJewelry { get; set; }
public bool ForbidSplitJewelry { get; set; }
}
/// <summary>
/// 包装规格
/// </summary>
public class PackageSpec
{
public string SpecCode { get; set; } = string.Empty;
public double Volume { get; set; }
public int StockQty { get; set; }
public double UnitCost { get; set; }
public int PriorityWeight { get; set; }
public double GetValidVolume(double coeff)
{
return Volume * coeff;
}
}
/// <summary>
/// 配载查询入参
/// </summary>
public class PackQueryCondition
{
public List<JewelryItem>? JewelryList { get; set; }
public PackConstraint? Constraint { get; set; }
public double TotalMaterialVol { get; set; }
public List<PackageSpec>? PackageSpecList { get; set; }
public LoadStrategyType StrategyType { get; set; }
public bool AllowOverLoad { get; set; }
}
/// <summary>
/// 使用的包装记录
/// </summary>
public class SingleUsePackage
{
public string SpecCode { get; set; } = string.Empty;
public double Volume { get; set; }
public double ValidVolume { get; set; }
public int UseCount { get; set; }
public string BelongJewelryCode { get; set; } = string.Empty;
}
/// <summary>
/// 配载结果
/// </summary>
public class PackResult
{
public int Code { get; set; }
public string Msg { get; set; } = string.Empty;
public List<SingleUsePackage> UsePackageList { get; set; } = new();
public double RemainMaterialVol { get; set; }
public Dictionary<string, int> RemainPackageStock { get; set; } = new();
public int TotalUsePackageCnt { get; set; }
public int SearchStateCount { get; set; }
public bool FullLoadFlag { get; set; }
public double JewelryTotalCalcVol { get; set; }
public double TotalCost { get; set; }
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : Logger.cs
*/
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
/// <summary>
/// 全局日志单例,同时输出控制台+日志文件
/// </summary>
public class AppLogger
{
private static AppLogger? _instance;
private static readonly object _lock = new();
private readonly string _logPath;
private AppLogger()
{
AppConfig.InitLogDir();
_logPath = Path.Combine(AppConfig.LogSavePath, AppConfig.LogFileName);
}
public static AppLogger GetInstance()
{
if (_instance == null)
{
lock (_lock)
{
_instance ??= new AppLogger();
}
}
return _instance;
}
private void Write(string level, string message)
{
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var line = $"[{time}] [{level}] {message}";
if (AppConfig.LogConsoleEnable)
{
Console.WriteLine(line);
}
if (AppConfig.LogFileEnable)
{
File.AppendAllText(_logPath, line + Environment.NewLine, Encoding.UTF8);
}
}
public void Info(string msg) => Write("INFO", msg);
public void Warn(string msg) => Write("WARN", msg);
public void Error(string msg) => Write("ERROR", msg);
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : IStrategy.cs
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
/// <summary>
/// 配载策略统一接口
/// </summary>
public interface IPackStrategy
{
PackResult Execute(PackQueryCondition condition);
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : BfsStrategy.cs
*/
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
public class BfsStrategy : IPackStrategy
{
private readonly AppLogger _logger = AppLogger.GetInstance();
private record BfsNode(
double LoadVol,
Dictionary<string, int> UsedMap,
double TotalCost,
int WeightSum,
int PackCount);
public PackResult Execute(PackQueryCondition condition)
{
double totalMatVol = condition.TotalMaterialVol;
var specList = condition.PackageSpecList!;
bool allowOver = condition.AllowOverLoad;
int maxState = AppConfig.MaxSearchState;
double bufferCoeff = condition.Constraint?.BufferCoeff ?? 1.0;
if (totalMatVol <= 0)
{
return new PackResult
{
Code = ResultCode.ParamError.Code,
Msg = ResultCode.ParamError.Msg,
RemainMaterialVol = totalMatVol
};
}
if (specList.Count == 0)
{
return new PackResult
{
Code = ResultCode.NoAvailablePackage.Code,
Msg = ResultCode.NoAvailablePackage.Msg,
RemainMaterialVol = totalMatVol
};
}
var initStock = new Dictionary<string, int>();
var specVolMap = new Dictionary<string, double>();
var specNominalVolMap = new Dictionary<string, double>();
var specCostMap = new Dictionary<string, double>();
var specWeightMap = new Dictionary<string, int>();
foreach (var item in specList)
{
initStock[item.SpecCode] = item.StockQty;
specVolMap[item.SpecCode] = item.Volume * bufferCoeff;
specNominalVolMap[item.SpecCode] = item.Volume;
specCostMap[item.SpecCode] = item.UnitCost;
specWeightMap[item.SpecCode] = item.PriorityWeight;
}
var startUsed = new Dictionary<string, int>();
foreach (var k in initStock.Keys) startUsed[k] = 0;
var visited = new HashSet<string>();
int visitedCount = 1;
var queue = new Queue<BfsNode>();
var startNode = new BfsNode(0, CloneDict(startUsed), 0, 0, 0);
queue.Enqueue(startNode);
visited.Add(GetStateKey(startUsed));
BfsNode? bestNode = null;
int? minPackCnt = null;
while (queue.Count > 0)
{
var node = queue.Dequeue();
if (node.LoadVol >= totalMatVol)
{
if (bestNode == null)
{
bestNode = node;
minPackCnt = node.PackCount;
}
else
{
if (node.PackCount == minPackCnt)
{
bool better = node.WeightSum > bestNode.WeightSum
|| (node.WeightSum == bestNode.WeightSum && node.TotalCost < bestNode.TotalCost);
if (better)
bestNode = node;
}
}
continue;
}
if (visitedCount >= maxState)
{
_logger.Warn($"搜索达到最大状态限制 {maxState},终止搜索");
break;
}
foreach (var kv in initStock)
{
string specCode = kv.Key;
int stockNum = kv.Value;
int usedNum = node.UsedMap[specCode];
if (usedNum >= stockNum) continue;
double packVol = specVolMap[specCode];
if (packVol <= 0) continue;
double newLoad = node.LoadVol + packVol;
if (!allowOver && newLoad > totalMatVol * 1.5) continue;
var newUsed = CloneDict(node.UsedMap);
newUsed[specCode]++;
string key = GetStateKey(newUsed);
if (visited.Contains(key)) continue;
visited.Add(key);
visitedCount++;
var newNode = new BfsNode(
newLoad,
newUsed,
node.TotalCost + specCostMap[specCode],
node.WeightSum + specWeightMap[specCode],
node.PackCount + 1);
queue.Enqueue(newNode);
}
}
if (bestNode == null)
{
return new PackResult
{
Code = ResultCode.NoAnyLoadPlan.Code,
Msg = ResultCode.NoAnyLoadPlan.Msg,
RemainMaterialVol = totalMatVol,
RemainPackageStock = CloneDict(initStock),
SearchStateCount = visitedCount
};
}
var best = bestNode;
double remainMat = totalMatVol - best.LoadVol;
bool fullFlag = remainMat <= 0.001;
var useList = new List<SingleUsePackage>();
var remainStock = new Dictionary<string, int>();
foreach (var kv in initStock)
{
string code = kv.Key;
int totalStock = kv.Value;
int useCnt = best.UsedMap[code];
remainStock[code] = totalStock - useCnt;
if (useCnt > 0)
{
useList.Add(new SingleUsePackage
{
SpecCode = code,
Volume = specNominalVolMap[code],
ValidVolume = specVolMap[code],
UseCount = useCnt
});
}
}
return new PackResult
{
Code = ResultCode.Success.Code,
Msg = ResultCode.Success.Msg,
UsePackageList = useList,
RemainMaterialVol = remainMat,
RemainPackageStock = remainStock,
TotalUsePackageCnt = best.PackCount,
SearchStateCount = visitedCount,
FullLoadFlag = fullFlag,
TotalCost = best.TotalCost
};
}
private static Dictionary<string, int> CloneDict(Dictionary<string, int> src)
{
return new Dictionary<string, int>(src);
}
private static string GetStateKey(Dictionary<string, int> dict)
{
return string.Join(",", dict.OrderBy(x => x.Key).Select(x => $"{x.Key}:{x.Value}"));
}
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : DfsStrategy.cs
*/
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
public class DfsStrategy : IPackStrategy
{
private readonly AppLogger _logger = AppLogger.GetInstance();
private double _totalMatVol;
private bool _allowOver;
private int _maxState;
private Dictionary<string, double> _specVolMap = new();
private Dictionary<string, double> _specNominalVolMap = new();
private Dictionary<string, double> _specCostMap = new();
private Dictionary<string, int> _initStock = new();
private DfsNode? _bestNode;
private HashSet<string> _visited = new();
private int _visitedCnt;
private record DfsNode(double LoadVol, Dictionary<string, int> UsedMap, double TotalCost, int PackCnt);
public PackResult Execute(PackQueryCondition condition)
{
_totalMatVol = condition.TotalMaterialVol;
_allowOver = condition.AllowOverLoad;
_maxState = AppConfig.MaxSearchState;
var specList = condition.PackageSpecList!;
double bufferCoeff = condition.Constraint?.BufferCoeff ?? 1.0;
if (_totalMatVol <= 0)
{
return new PackResult { Code = ResultCode.ParamError.Code, Msg = ResultCode.ParamError.Msg };
}
if (specList.Count == 0)
{
return new PackResult { Code = ResultCode.NoAvailablePackage.Code, Msg = ResultCode.NoAvailablePackage.Msg };
}
_initStock.Clear();
_specVolMap.Clear();
_specNominalVolMap.Clear();
_specCostMap.Clear();
foreach (var item in specList)
{
_initStock[item.SpecCode] = item.StockQty;
_specVolMap[item.SpecCode] = item.Volume * bufferCoeff;
_specNominalVolMap[item.SpecCode] = item.Volume;
_specCostMap[item.SpecCode] = item.UnitCost;
}
var startUsed = new Dictionary<string, int>();
foreach (var k in _initStock.Keys) startUsed[k] = 0;
_visited.Clear();
_visitedCnt = 0;
_bestNode = null;
Dfs(0, startUsed, 0, 0);
if (_bestNode == null)
{
return new PackResult
{
Code = ResultCode.NoAnyLoadPlan.Code,
Msg = ResultCode.NoAnyLoadPlan.Msg,
RemainMaterialVol = _totalMatVol,
RemainPackageStock = new Dictionary<string, int>(_initStock),
SearchStateCount = _visitedCnt
};
}
var best = _bestNode;
double remainMat = _totalMatVol - best.LoadVol;
bool fullFlag = remainMat <= 0.001;
var useList = new List<SingleUsePackage>();
var remainStock = new Dictionary<string, int>();
foreach (var kv in _initStock)
{
string code = kv.Key;
int totalStock = kv.Value;
int useCnt = best.UsedMap[code];
remainStock[code] = totalStock - useCnt;
if (useCnt > 0)
{
useList.Add(new SingleUsePackage
{
SpecCode = code,
Volume = _specNominalVolMap[code],
ValidVolume = _specVolMap[code],
UseCount = useCnt
});
}
}
return new PackResult
{
Code = ResultCode.Success.Code,
Msg = ResultCode.Success.Msg,
UsePackageList = useList,
RemainMaterialVol = remainMat,
RemainPackageStock = remainStock,
TotalUsePackageCnt = best.PackCnt,
SearchStateCount = _visitedCnt,
FullLoadFlag = fullFlag,
TotalCost = best.TotalCost
};
}
private void Dfs(double loadVol, Dictionary<string, int> usedMap, double cost, int packCnt)
{
if (_bestNode != null) return;
if (loadVol >= _totalMatVol)
{
_bestNode = new DfsNode(loadVol, new Dictionary<string, int>(usedMap), cost, packCnt);
return;
}
string key = GetStateKey(usedMap);
if (_visited.Contains(key)) return;
if (_visitedCnt >= _maxState) return;
_visited.Add(key);
_visitedCnt++;
foreach (var kv in _initStock)
{
string specCode = kv.Key;
int stockNum = kv.Value;
int usedNum = usedMap[specCode];
if (usedNum >= stockNum) continue;
double pv = _specVolMap[specCode];
if (pv <= 0) continue;
double newLoad = loadVol + pv;
if (!_allowOver && newLoad > _totalMatVol * 1.5) continue;
usedMap[specCode]++;
Dfs(newLoad, usedMap, cost + _specCostMap[specCode], packCnt + 1);
usedMap[specCode]--;
}
}
private static string GetStateKey(Dictionary<string, int> dict)
{
return string.Join(",", dict.OrderBy(x => x.Key).Select(x => $"{x.Key}:{x.Value}"));
}
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : AStarStrategy.cs
*/
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Core
{
public class AStarStrategy : IPackStrategy
{
private readonly AppLogger _logger = AppLogger.GetInstance();
private record AStarItem(double F, int G, double LoadVol, double Cost, Dictionary<string, int> UsedMap);
public PackResult Execute(PackQueryCondition condition)
{
double totalMatVol = condition.TotalMaterialVol;
var specList = condition.PackageSpecList!;
bool allowOver = condition.AllowOverLoad;
int maxState = AppConfig.MaxSearchState;
double bufferCoeff = condition.Constraint?.BufferCoeff ?? 1.0;
if (totalMatVol <= 0)
{
return new PackResult { Code = ResultCode.ParamError.Code, Msg = ResultCode.ParamError.Msg };
}
if (specList.Count == 0)
{
return new PackResult { Code = ResultCode.NoAvailablePackage.Code, Msg = ResultCode.NoAvailablePackage.Msg };
}
var initStock = new Dictionary<string, int>();
var specVolMap = new Dictionary<string, double>();
var specNominalVolMap = new Dictionary<string, double>();
var specCostMap = new Dictionary<string, double>();
var specWeightMap = new Dictionary<string, int>();
foreach (var item in specList)
{
initStock[item.SpecCode] = item.StockQty;
specVolMap[item.SpecCode] = item.Volume * bufferCoeff;
specNominalVolMap[item.SpecCode] = item.Volume;
specCostMap[item.SpecCode] = item.UnitCost;
specWeightMap[item.SpecCode] = item.PriorityWeight;
}
var startUsed = new Dictionary<string, int>();
foreach (var k in initStock.Keys) startUsed[k] = 0;
var visited = new HashSet<string>();
int visitedCount = 1;
var heap = new PriorityQueue<AStarItem, double>();
heap.Enqueue(new AStarItem(0, 0, 0, 0, new Dictionary<string, int>(startUsed)), 0);
AStarItem? best = null;
while (heap.Count > 0)
{
heap.TryDequeue(out var top, out _);
string key = GetStateKey(top.UsedMap);
if (visited.Contains(key)) continue;
visited.Add(key);
visitedCount++;
if (top.LoadVol >= totalMatVol)
{
best = top;
break;
}
if (visitedCount >= maxState)
{
_logger.Warn("A*搜索达到最大状态限制");
break;
}
foreach (var kv in initStock)
{
string specCode = kv.Key;
int stockNum = kv.Value;
int usedNum = top.UsedMap[specCode];
if (usedNum >= stockNum) continue;
double pv = specVolMap[specCode];
if (pv <= 0) continue;
double newLoad = top.LoadVol + pv;
if (!allowOver && newLoad > totalMatVol * 1.5) continue;
int newG = top.G + 1;
double newCost = top.Cost + specCostMap[specCode];
double h = Math.Max(totalMatVol - newLoad, 0) * 0.01 + newCost * 0.15 - specWeightMap[specCode] * 0.005;
double newF = newG + h;
var newUsed = new Dictionary<string, int>(top.UsedMap);
newUsed[specCode]++;
heap.Enqueue(new AStarItem(newF, newG, newLoad, newCost, newUsed), newF);
}
}
if (best == null)
{
return new PackResult
{
Code = ResultCode.NoAnyLoadPlan.Code,
Msg = ResultCode.NoAnyLoadPlan.Msg,
RemainMaterialVol = totalMatVol,
RemainPackageStock = new Dictionary<string, int>(initStock),
SearchStateCount = visitedCount
};
}
double remainMat = totalMatVol - best.LoadVol;
bool fullFlag = remainMat <= 0.001;
var useList = new List<SingleUsePackage>();
var remainStock = new Dictionary<string, int>();
foreach (var kv in initStock)
{
string code = kv.Key;
int totalStock = kv.Value;
int useCnt = best.UsedMap[code];
remainStock[code] = totalStock - useCnt;
if (useCnt > 0)
{
useList.Add(new SingleUsePackage
{
SpecCode = code,
Volume = specNominalVolMap[code],
ValidVolume = specVolMap[code],
UseCount = useCnt
});
}
}
return new PackResult
{
Code = ResultCode.Success.Code,
Msg = ResultCode.Success.Msg,
UsePackageList = useList,
RemainMaterialVol = remainMat,
RemainPackageStock = remainStock,
TotalUsePackageCnt = best.G,
SearchStateCount = visitedCount,
FullLoadFlag = fullFlag,
TotalCost = best.Cost
};
}
private static string GetStateKey(Dictionary<string, int> dict)
{
return string.Join(",", dict.OrderBy(x => x.Key).Select(x => $"{x.Key}:{x.Value}"));
}
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : SafeTaskPool.cs
*/
using CSharpAlgorithms.BreadthFirst.Config;
using CSharpAlgorithms.BreadthFirst.Core;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Concurrency
{
/// <summary>
/// 线程安全任务池
/// </summary>
public class SafeTaskPool
{
private static SafeTaskPool? _instance;
private static readonly object _lockObj = new();
private readonly SemaphoreSlim _sem;
private SafeTaskPool(int maxWorker)
{
_sem = new SemaphoreSlim(maxWorker, maxWorker);
}
public static SafeTaskPool GetInstance()
{
if (_instance == null)
{
lock (_lockObj)
{
_instance ??= new SafeTaskPool(AppConfig.MaxWorkerThread);
}
}
return _instance;
}
public async Task<PackResult> SubmitAsync(IPackStrategy strategy, PackQueryCondition condition)
{
await _sem.WaitAsync();
try
{
return await Task.Run(() => strategy.Execute(condition));
}
finally
{
_sem.Release();
}
}
public PackResult Submit(IPackStrategy strategy, PackQueryCondition condition)
{
return SubmitAsync(strategy, condition).Result;
}
}
}
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : PackageService.cs
*/
using CSharpAlgorithms.BreadthFirst.Concurrency;
using CSharpAlgorithms.BreadthFirst.Core;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.BreadthFirst.Service
{
/// <summary>
///
/// </summary>
public class JewelryPackageService
{
private readonly AppLogger _logger = AppLogger.GetInstance();
/// <summary>
/// 汇总首饰总体积
/// </summary>
public double CalcJewelryTotalVolume(List<JewelryItem> list)
{
double total = 0;
_logger.Info("=====开始计算各类首饰物料体积明细=====");
foreach (var item in list)
{
double vol = item.GetTotalVolume();
_logger.Info($"货号:{item.JewelryCode} | 单件体积:{item.SingleVolume} cm³ | 数量:{item.Quantity} | 品类合计:{vol:F2} cm³");
total += vol;
}
_logger.Info($"【汇总完成】所有首饰物料理论总体积 = {total:F2} cm³");
return total;
}
/// <summary>
/// 禁止拆盒校验:检查是否存在一种包装规格,可用多个同规格盒子完整容纳整个品类
/// </summary>
public bool CheckForbidSplitConstraint(JewelryItem item, List<PackageSpec> packages, double coeff)
{
double totalV = item.GetTotalVolume();
double singleV = item.SingleVolume;
foreach (var pkg in packages)
{
double validVol = pkg.GetValidVolume(coeff);
if (singleV <= validVol && pkg.StockQty * validVol >= totalV)
return true;
}
_logger.Error($"【约束冲突】首饰{item.JewelryCode}总体积{totalV:F2},无盒子规格可完整容纳(单件体积{singleV:F2}),禁止拆盒模式无解");
return false;
}
/// <summary>
/// 禁止混装:拆分为多个独立配载任务
/// </summary>
public List<PackQueryCondition> SplitByJewelry(PackQueryCondition origin)
{
var cons = origin.Constraint;
if (cons == null || !cons.ForbidMixJewelry)
{
return new List<PackQueryCondition> { origin };
}
_logger.Info("【启用禁止混装规则】各首饰品类独立计算包装方案");
var subList = new List<PackQueryCondition>();
foreach (var j in origin.JewelryList!)
{
if (cons.ForbidSplitJewelry)
{
if (!CheckForbidSplitConstraint(j, origin.PackageSpecList!, cons.BufferCoeff))
continue;
}
if (cons.SingleBoxMaxQty == 1)
{
for (int i = 0; i < j.Quantity; i++)
{
var singleItem = new JewelryItem
{
JewelryCode = j.JewelryCode,
SingleVolume = j.SingleVolume,
Quantity = 1
};
subList.Add(new PackQueryCondition
{
JewelryList = new List<JewelryItem> { singleItem },
Constraint = cons,
TotalMaterialVol = j.SingleVolume,
PackageSpecList = origin.PackageSpecList,
StrategyType = origin.StrategyType,
AllowOverLoad = origin.AllowOverLoad
});
}
}
else
{
subList.Add(new PackQueryCondition
{
JewelryList = new List<JewelryItem> { j },
Constraint = cons,
TotalMaterialVol = j.GetTotalVolume(),
PackageSpecList = origin.PackageSpecList,
StrategyType = origin.StrategyType,
AllowOverLoad = origin.AllowOverLoad
});
}
}
return subList;
}
public IPackStrategy CreateStrategy(LoadStrategyType type)
{
return type switch
{
LoadStrategyType.BFS => new BfsStrategy(),
LoadStrategyType.DFS => new DfsStrategy(),
LoadStrategyType.ASTAR => new AStarStrategy(),
_ => throw new NotSupportedException($"不支持策略 {type}")
};
}
public PackResult CalcSinglePlan(PackQueryCondition condition)
{
if (condition.Constraint != null)
{
var c = condition.Constraint;
_logger.Info("=======装箱约束参数=======");
_logger.Info($"缓冲空隙系数:{c.BufferCoeff}");
_logger.Info($"单盒最大首饰件数:{c.SingleBoxMaxQty}");
_logger.Info($"禁止不同品类混装:{c.ForbidMixJewelry}");
_logger.Info($"禁止同品类拆盒:{c.ForbidSplitJewelry}");
}
double calcVol = 0;
if (condition.JewelryList != null && condition.JewelryList.Count > 0)
{
calcVol = CalcJewelryTotalVolume(condition.JewelryList);
condition.TotalMaterialVol = calcVol;
}
var subConditions = SplitByJewelry(condition);
var strategy = CreateStrategy(condition.StrategyType);
var pool = SafeTaskPool.GetInstance();
var subResults = new List<PackResult>();
foreach (var subCond in subConditions)
{
subResults.Add(pool.Submit(strategy, subCond));
}
var mergeResult = MergeSubResult(subResults, condition);
mergeResult.JewelryTotalCalcVol = calcVol;
PrintResultLog(mergeResult);
return mergeResult;
}
/// <summary>
/// 合并多个品类独立配载结果
/// </summary>
public PackResult MergeSubResult(List<PackResult> subList, PackQueryCondition origin)
{
var usePackages = new List<SingleUsePackage>();
var remainStock = new Dictionary<string, int>();
double remainMatSum = 0;
int totalSearch = 0;
double totalCostSum = 0;
bool allSuccess = true;
foreach (var pkg in origin.PackageSpecList!)
remainStock[pkg.SpecCode] = pkg.StockQty;
foreach (var sub in subList)
{
if (sub.Code != ResultCode.Success.Code) allSuccess = false;
remainMatSum += sub.RemainMaterialVol;
totalSearch += sub.SearchStateCount;
totalCostSum += sub.TotalCost;
usePackages.AddRange(sub.UsePackageList);
foreach (var pu in sub.UsePackageList)
remainStock[pu.SpecCode] -= pu.UseCount;
}
int totalPackCnt = usePackages.Sum(x => x.UseCount);
bool fullFlag = remainMatSum <= 0.01;
if (!allSuccess)
{
return new PackResult
{
Code = ResultCode.NoAnyLoadPlan.Code,
Msg = "部分品类无法找到可行包装方案",
UsePackageList = usePackages,
RemainMaterialVol = Math.Round(remainMatSum, 2),
RemainPackageStock = remainStock,
TotalUsePackageCnt = totalPackCnt,
SearchStateCount = totalSearch,
FullLoadFlag = fullFlag,
TotalCost = totalCostSum
};
}
return new PackResult
{
Code = ResultCode.Success.Code,
Msg = ResultCode.Success.Msg,
UsePackageList = usePackages,
RemainMaterialVol = Math.Round(remainMatSum, 2),
RemainPackageStock = remainStock,
TotalUsePackageCnt = totalPackCnt,
SearchStateCount = totalSearch,
FullLoadFlag = fullFlag,
TotalCost = totalCostSum
};
}
public void PrintResultLog(PackResult res)
{
_logger.Info("=====================配载结果=====================");
_logger.Info($"首饰明细汇总理论总体积:{res.JewelryTotalCalcVol:F2} cm³");
_logger.Info($"返回码:{res.Code} 消息:{res.Msg}");
_logger.Info($"搜索遍历状态总数:{res.SearchStateCount}");
_logger.Info($"总共使用包装数量:{res.TotalUsePackageCnt}");
_logger.Info($"方案预估总成本:{res.TotalCost:F2}");
_logger.Info($"物料是否完全装满:{res.FullLoadFlag}");
_logger.Info($"剩余未装入物料体积:{res.RemainMaterialVol:F2} cm³");
_logger.Info("本次选用包装清单:");
foreach (var item in res.UsePackageList)
{
_logger.Info($" 编码:{item.SpecCode} 标称容积:{item.Volume:F2}cm³ 有效容积:{item.ValidVolume:F2}cm³ 使用数量:{item.UseCount} 装载品类:{item.BelongJewelryCode}");
}
_logger.Info("包装库存剩余情况:");
foreach (var kv in res.RemainPackageStock)
{
_logger.Info($" {kv.Key} 剩余库存:{kv.Value}");
}
_logger.Info("==================================================");
}
}
}
调用;
cs
/*
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author : geovindu,Geovin Du 涂聚文.
# IDE : vs2026 c# .net 10
# os : windows 10
# database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j
# Datetime : 2026/07/18 23:16
# User : geovindu
# Product : Visual Studio 2026
# Project : CSharpAlgorithms
# File : BreadthFirstBll.cs
*/
using CSharpAlgorithms.BreadthFirst.Core;
using CSharpAlgorithms.BreadthFirst.Service;
using System;
using System.Collections.Generic;
using System.Text;
namespace CSharpAlgorithms.Bll
{
public class BreadthFirstBll
{
static readonly AppLogger Log = AppLogger.GetInstance();
static readonly JewelryPackageService Service = new();
static double ReadDouble(string prompt)
{
while (true)
{
Console.Write(prompt);
var line = Console.ReadLine();
if (double.TryParse(line, out var v) && v > 0)
return v;
Console.WriteLine("输入错误,请输入大于0的数字!");
}
}
static int ReadInt(string prompt)
{
while (true)
{
Console.Write(prompt);
var line = Console.ReadLine();
if (int.TryParse(line, out var v) && v >= 0)
return v;
Console.WriteLine("输入错误,请输入非负整数!");
}
}
static bool ReadBool(string prompt)
{
while (true)
{
Console.Write(prompt + " y/n:");
var line = Console.ReadLine()?.Trim().ToLower();
if (line == "y") return true;
if (line == "n") return false;
Console.WriteLine("只能输入 y 或 n");
}
}
/// <summary>
///
/// </summary>
public void Demo()
{
List<JewelryItem> jewelryList = new();
List<PackageSpec> pkgList = new();
PackConstraint? constraint = null;
while (true)
{
Console.WriteLine("\n================珠宝首饰智能包装配载系统 C#版================");
Console.WriteLine("1. 录入首饰品类清单");
Console.WriteLine("2. 录入包装规格(容积、成本、优先级权重、库存)");
Console.WriteLine("3. 设置装箱约束规则");
Console.WriteLine("4. BFS配载(推荐:最少数量+权重择优+成本择优)");
Console.WriteLine("5. DFS配载");
Console.WriteLine("6. A*启发式配载");
Console.WriteLine("0. 退出程序");
Console.Write("请输入选项:");
var opt = Console.ReadLine()?.Trim() ?? "";
switch (opt)
{
case "1":
jewelryList.Clear();
while (true)
{
Console.Write("首饰货号:");
string code = Console.ReadLine()!.Trim();
double sv = ReadDouble("单件首饰(含配件)体积 cm³:");
int qty = ReadInt("该品类首饰数量:");
jewelryList.Add(new JewelryItem
{
JewelryCode = code,
SingleVolume = sv,
Quantity = qty
});
if (!ReadBool("继续录入下一类首饰?")) break;
}
break;
case "2":
pkgList.Clear();
while (true)
{
Console.Write("包装编码(BOX-500):");
string code = Console.ReadLine()!.Trim();
double vol = ReadDouble("包装标称容积 cm³:");
double cost = ReadDouble("单个包装单位成本:");
int weight = ReadInt("优先级权重(数值越大优先选用,建议1~100):");
int stock = ReadInt("当前可用库存数量:");
pkgList.Add(new PackageSpec
{
SpecCode = code,
Volume = vol,
UnitCost = cost,
PriorityWeight = weight,
StockQty = stock
});
if (!ReadBool("继续新增包装?")) break;
}
break;
case "3":
double bufferCoeff = ReadDouble("缓冲空隙系数(0.7~0.95,例0.85):");
int maxBoxQty = ReadInt("单个礼盒最多放置首饰件数:");
bool forbidMix = ReadBool("是否禁止不同品类首饰混装同一礼盒");
bool forbidSplit = ReadBool("是否禁止同一品类首饰拆分多个盒子");
constraint = new PackConstraint
{
BufferCoeff = bufferCoeff,
SingleBoxMaxQty = maxBoxQty,
ForbidMixJewelry = forbidMix,
ForbidSplitJewelry = forbidSplit
};
Log.Info("装箱约束参数设置完成!");
break;
case "4":
case "5":
case "6":
if (jewelryList.Count == 0 || pkgList.Count == 0 || constraint == null)
{
Console.WriteLine("请先录入首饰、包装、装箱约束!");
break;
}
LoadStrategyType st = opt switch
{
"4" => LoadStrategyType.BFS,
"5" => LoadStrategyType.DFS,
"6" => LoadStrategyType.ASTAR,
_ => LoadStrategyType.BFS
};
var cond = new PackQueryCondition
{
JewelryList = jewelryList,
Constraint = constraint,
PackageSpecList = pkgList,
StrategyType = st,
AllowOverLoad = false
};
Service.CalcSinglePlan(cond);
break;
case "0":
Log.Info("程序正常退出");
return;
default:
Console.WriteLine("无效选项!");
break;
}
}
}
}
}
输出:
