public void TestWeightVariance() { 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(25, packedBoxList.GetWeightVariance()); }
/// <summary> /// Not every attempted repack is actually helpful - sometimes moving an item between two /// otherwise identical boxes, or sometimes the box used for the now lighter set of items /// actually weighs more when empty causing an increase in total weight. /// </summary> /// <returns></returns> private bool DidRepackActuallyHelp(PackedBox oldBoxA, PackedBox oldBoxB, PackedBox newBoxA, PackedBox newBoxB) { PackedBoxList oldList = new PackedBoxList(); oldList.InsertFromArray(new PackedBox[] { oldBoxA, oldBoxB }); PackedBoxList newList = new PackedBoxList(); newList.InsertFromArray(new PackedBox[] { newBoxA, newBoxB }); return(newList.GetWeightVariance() < oldList.GetWeightVariance()); }
/// <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); }