public void TestMeanWeight() { var box = Factory.CreateBox("Box", 10, 10, 10, 0, 10, 10, 10, 100); var itemA = Factory.CreateItem("Item A", 5, 10, 10, 10, true); var itemB = Factory.CreateItem("Item B", 5, 10, 10, 20, true); var packedItemA = new PackedItem(itemA, 0, 0, 0, 5, 10, 10); var packedItemB = new PackedItem(itemB, 0, 0, 0, 5, 10, 10); var packedItemListA = new PackedItemList(); packedItemListA.Insert(packedItemA); var packedBoxA = new PackedBox(box, packedItemListA); var packedItemListB = new PackedItemList(); packedItemListB.Insert(packedItemB); var packedBoxB = new PackedBox(box, packedItemListB); var pBoxArray = new PackedBox[] { packedBoxA, packedBoxB }; var packedBoxList = new PackedBoxList(); packedBoxList.Insert(packedBoxA); packedBoxList.Insert(packedBoxB); Assert.Equal(15, packedBoxList.GetMeanWeight()); }
/// <summary> /// Given a solution set of packed boxes, repack them to achieve optimum weight distribution. /// </summary> /// <param name="originalBoxes"></param> /// <returns></returns> public PackedBoxList RedistributeWeight(PackedBoxList originalBoxes) { var targetWeight = originalBoxes.GetMeanWeight(); var redistrebutedBoxes = originalBoxes.ToList(); redistrebutedBoxes.OrderByDescending(box => box.TotalWeight); var iterationSuccessful = false; do { iterationSuccessful = false; var a = redistrebutedBoxes.Count; while (a - 1 >= 0) { a -= 1; var boxA = redistrebutedBoxes[a]; var b = redistrebutedBoxes.Count; while (b - 1 >= 0) { b -= 1; var boxB = redistrebutedBoxes[b]; if (b <= a || boxA.TotalWeight == boxB.TotalWeight) { continue; //no need to evaluate } iterationSuccessful = EqualiseWeight(ref boxA, ref boxB, targetWeight); redistrebutedBoxes[a] = boxA; redistrebutedBoxes[b] = boxB; if (iterationSuccessful) { //remove any now-empty boxes from the list redistrebutedBoxes = redistrebutedBoxes .Where(box => box != null) .OrderByDescending(box => box.TotalWeight) .ToList(); goto LEAVE_LOOPS; } } } LEAVE_LOOPS :; } while (iterationSuccessful); //Combine back into a single list var packedBoxes = new PackedBoxList(); packedBoxes.InsertFromArray(redistrebutedBoxes.ToArray()); return(packedBoxes); }
/// <summary> /// Given a solution set of packed boxes, repack them to achieve optimum weight distribution /// </summary> /// <param name="originalPackedBoxList"></param> /// <returns></returns> public PackedBoxList RedistributeWeight(PackedBoxList originalPackedBoxList) { var targetWeight = originalPackedBoxList.GetMeanWeight(); _logger.Log(LogLevel.Debug, "Repacking for weight distribution, weight variance {0}, target weight {1}", originalPackedBoxList.GetWeightVariance(), targetWeight); var packedBoxes = new PackedBoxList(); var overWeightBoxes = new List <PackedBox>(); var underWeightBoxes = new List <PackedBox>(); var originalPackedBoxes = originalPackedBoxList.ShallowCopy().GetContent().Cast <PackedBox>(); foreach (var originalPackedBox in originalPackedBoxes) { var boxWeight = originalPackedBox.GetWeight(); if (boxWeight > targetWeight) { overWeightBoxes.Add(originalPackedBox); } else if (boxWeight < targetWeight) { underWeightBoxes.Add(originalPackedBox); } else { packedBoxes.Insert(originalPackedBox); // Target weight, so we'll keep these } } // Keep moving items from most overweight box to most underweight box var tryRepack = false; do { _logger.Log(LogLevel.Debug, "Boxes under/over target: {0}/{1}", underWeightBoxes.Count, overWeightBoxes.Count); for (var underWeightBoxIndex = 0; underWeightBoxIndex < underWeightBoxes.Count; underWeightBoxIndex++) { var underWeightBox = underWeightBoxes[underWeightBoxIndex]; for (var overWeightBoxIndex = 0; overWeightBoxIndex < overWeightBoxes.Count; overWeightBoxIndex++) { var overWeightBox = overWeightBoxes[overWeightBoxIndex]; var overWeightBoxItems = overWeightBox.GetItems().GetContent().Cast <Item>().ToList(); foreach (var overWeightBoxItem in overWeightBoxItems) { // If over target weight, just continue as it would hinder rather than help weight distribution var overTargetWeight = (underWeightBox.GetWeight() + overWeightBoxItem.Weight) > targetWeight; if (overTargetWeight) { continue; } var newItemsForLighterBox = underWeightBox.GetItems().ShallowCopy(); newItemsForLighterBox.Insert(overWeightBoxItem); // We may need a bigger box var newLighterBoxPacker = new Packer(); newLighterBoxPacker.AddBoxes(Boxes); newLighterBoxPacker.AddItems(newItemsForLighterBox); var newLighterBox = newLighterBoxPacker.PackByVolume().ExtractMin(); // New item fits! if (newLighterBox.GetItems().GetCount() == newItemsForLighterBox.GetCount()) { // Remove from overWeightBoxItems as it is now packed in a different box overWeightBoxItems.Remove(overWeightBoxItem); // We may be able to use a smaller box var newHeavierBoxPacker = new Packer(); newHeavierBoxPacker.AddBoxes(Boxes); newHeavierBoxPacker.AddItems(overWeightBoxItems); var newHeavierBoxes = newHeavierBoxPacker.PackByVolume(); // Found an edge case in packing algorithm that *increased* box count if (newHeavierBoxes.GetCount() > 1) { _logger.Log(LogLevel.Info, "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less effciient than original"); return(originalPackedBoxList); } // TODO: INDEX BASED ARRAY INSERTION FOR BOTH overWeightBoxes[overWeightBoxIndex] = newHeavierBoxes.ExtractMin(); underWeightBoxes[underWeightBoxIndex] = newLighterBox; // We did some work, so see if we can do even better tryRepack = true; overWeightBoxes.Sort(packedBoxes.ReverseCompareTo); underWeightBoxes.Sort(packedBoxes.ReverseCompareTo); // The devil, but ported from PHP, was originally break 3, but .NET doesn't have a break x command // so we're using a goto statement. It is, however, more readable than break 3. goto MOVINGON; } } } MOVINGON : _logger.Log(LogLevel.Info, "Trying to repack"); } } while (tryRepack); packedBoxes.InsertAll(overWeightBoxes); packedBoxes.InsertAll(underWeightBoxes); return(packedBoxes); }