如何使用LINQ正确合并两个列表?同长度PackItem匹配异常问题求助
解决LINQ合并两个列表时的数量精准匹配问题
我懂你的问题了——你现在的LINQ写法用了笛卡尔积,把所有同长度的ProductionInfo和PackItem都做了配对,所以生成了一堆多余的条目,完全不符合你要的精准数量匹配需求。要实现按长度分组后,逐个消耗两边的数量来生成唯一条目,我们需要换一种思路,而不是简单的全排列配对。
问题根源分析
你的代码里用了SelectMany来关联两个列表,再过滤同长度的条目:
var merged = productionInfoList.SelectMany(x => packItems, (x, y) => new { x, y }) .Where(i => i.x.Length == i.y.Length) .Select(i => i.x.AddID(i.y));
这会把每个同长度的ProductionInfo和所有同长度的PackItem组合一遍。比如Length=4的场景,2个ProductionInfo和2个PackItem就会生成4个组合,但我们只需要把两边的数量一一对应消耗完,生成2个条目。
解决方案:按长度分组+数量消耗式匹配
我们可以先按Length分组,然后对每个分组内的条目,逐个消耗双方的数量,直到用完。下面提供两种实现方式:
方式一:可读性优先的foreach版本(推荐)
这种写法逻辑清晰,容易维护,完全符合你的需求:
private void DoTest() { // 原有的packItems和productionInfoList初始化代码不变... // 按Length分组,准备处理每个长度的匹配 var packGroups = packItems.GroupBy(p => p.Length).ToDictionary(g => g.Key, g => g.ToList()); var prodGroups = productionInfoList.GroupBy(p => p.Length).ToDictionary(g => g.Key, g => g.ToList()); var merged = new List<ProductionInfo>(); foreach (var length in packGroups.Keys) { var packsEnumerator = packGroups[length].GetEnumerator(); packsEnumerator.MoveNext(); var currentPack = packsEnumerator.Current; int remainingPackQty = currentPack.Quantity; foreach (var prod in prodGroups[length]) { int remainingProdQty = prod.Quantity; while (remainingProdQty > 0) { // 取当前能分配的最小数量 int qtyToAssign = Math.Min(remainingPackQty, remainingProdQty); merged.Add(new ProductionInfo { LineID = currentPack.ID, CoilNum = prod.CoilNum, Quantity = qtyToAssign, Length = length }); // 更新剩余数量 remainingPackQty -= qtyToAssign; remainingProdQty -= qtyToAssign; // 当前PackItem数量用完,切换到下一个 if (remainingPackQty == 0 && packsEnumerator.MoveNext()) { currentPack = packsEnumerator.Current; remainingPackQty = currentPack.Quantity; } } } } // 输出结果 Console.WriteLine("ID | Coil | Qty | Length"); foreach (var item in merged) { Console.WriteLine($"{item.LineID} | {item.CoilNum} | {item.Quantity} | {item.Length}"); } }
方式二:纯LINQ实现(利用Aggregate跟踪状态)
如果你坚持要用LINQ写法,可以用Aggregate来累积状态(剩余的PackItem枚举器、已合并的列表),不过可读性稍差:
private void DoTest() { // 原有的packItems和productionInfoList初始化代码不变... var merged = packItems.GroupBy(p => p.Length) .Join(productionInfoList.GroupBy(p => p.Length), packGroup => packGroup.Key, prodGroup => prodGroup.Key, (packGroup, prodGroup) => new { Length = packGroup.Key, Packs = packGroup.ToList(), Prods = prodGroup.ToList() }) .SelectMany(g => g.Prods.Aggregate( new { RemainingPacks = g.Packs.GetEnumerator(), MergedItems = new List<ProductionInfo>() }, (state, prod) => { int remainingProdQty = prod.Quantity; if (!state.RemainingPacks.MoveNext() && remainingProdQty > 0) return state; // 输入已保证数量匹配,理论不会走到这里 var currentPack = state.RemainingPacks.Current; int remainingPackQty = currentPack.Quantity; while (remainingProdQty > 0) { int qty = Math.Min(remainingPackQty, remainingProdQty); state.MergedItems.Add(new ProductionInfo { LineID = currentPack.ID, CoilNum = prod.CoilNum, Quantity = qty, Length = g.Length }); remainingPackQty -= qty; remainingProdQty -= qty; if (remainingPackQty == 0 && state.RemainingPacks.MoveNext()) { currentPack = state.RemainingPacks.Current; remainingPackQty = currentPack.Quantity; } } return state; }, state => state.MergedItems)) .ToList(); // 输出结果 Console.WriteLine("ID | Coil | Qty | Length"); foreach (var item in merged) { Console.WriteLine($"{item.LineID} | {item.CoilNum} | {item.Quantity} | {item.Length}"); } }
输出结果
两种方式都会生成你预期的正确输出:
ID | Coil | Qty | Length 4 | A | 4 | 10 4 | B | 1 | 10 5 | B | 2 | 4 6 | A | 1 | 4
内容的提问来源于stack exchange,提问作者Neil B
相关产品推荐
相关产品推荐

