public void TestTop() { 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(packedBoxA, packedBoxList.Top()); }
public void TestInsertAndCount() { 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 packedBoxList = new PackedBoxList(); packedBoxList.Insert(packedBoxA); packedBoxList.Insert(packedBoxB); Assert.Equal(2, packedBoxList.Count); }
public PackedBoxList DoVolumePacking() { var packedBoxes = new PackedBoxList(); //Keep going until everything packed while (_items.Count > 0) { var packedBoxesIteration = new List <PackedBox>(); //Loop through boxes starting with smallest, see what happens foreach (var box in _boxes) { var volumePacker = new VolumePacker(box, _items.Clone()); var packedBox = volumePacker.Pack(); if (packedBox.PackedItems.Count != 0) { packedBoxesIteration.Add(packedBox); //Have we found a single box that contains everything? if (packedBox.PackedItems.Count == _items.Count) { break; } } } // if any items is packed, then any chanses for this in next iteration if (packedBoxesIteration.Count == 0) { var ex = new ItemTooLargeException(); ex.Data.Add("item", _items.Top()); throw ex; } //Find best box of iteration, and remove packed items from unpacked list var bestBox = FindBestBoxFromIteration(packedBoxesIteration); foreach (var itemToRemove in bestBox.PackedItems) { _items.Remove(itemToRemove.Item); } packedBoxes.Insert(bestBox); } return(packedBoxes); }
public void TestVolumeUtilisation() { var box = Factory.CreateBox("Box", 10, 10, 10, 0, 10, 10, 10, 10); var item = Factory.CreateItem("Item", 5, 10, 10, 10, true); var packedItem = new PackedItem(item, 0, 0, 0, 5, 10, 10); var packedItemList = new PackedItemList(); packedItemList.Insert(packedItem); var packedBox = new PackedBox(box, packedItemList); var packedBoxList = new PackedBoxList(); packedBoxList.Insert(packedBox); Assert.Equal(50f, packedBoxList.GetVolumeUtilisation()); }
/// <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); }
/// <summary> /// Pack items into boxes using the principle of largest volume item first /// </summary> /// <returns></returns> public PackedBoxList PackByVolume() { var packedBoxes = new PackedBoxList(); while (Items.GetCount() > 0) { var boxesToEvaluate = Boxes.ShallowCopy(); var boxListToEvaluate = Boxes.ShallowCopy().GetContent().Cast <Box>().ToList(); var packedBoxesIteration = new PackedBoxList(); if (!StartWithLargerBoxes) { while (!boxesToEvaluate.IsEmpty()) { var box = boxesToEvaluate.ExtractMin(); var packedBox = PackIntoBox(box, Items.ShallowCopy()); if (packedBox.GetItems().GetCount() > 0) { packedBoxesIteration.Insert(packedBox); if (packedBox.GetItems().GetCount() == Items.GetCount()) { break; } } } } else { boxListToEvaluate.Sort(boxesToEvaluate.ReverseCompareTo); foreach (var box in boxListToEvaluate) { var packedBox = PackIntoBox(box, Items.ShallowCopy()); if (packedBox.GetItems().GetCount() > 0) { packedBoxesIteration.Insert(packedBox); } } } // Check iteration was productive if (packedBoxesIteration.IsEmpty() && !CreateBoxesForOversizedItems) { var oversizedItem = Items.GetMax(); throw new ItemTooLargeException($"Item {oversizedItem.Description} (id: {oversizedItem.Id}) is too large to fit into any box."); } // 1. Create box, add to boxes list // 2. Run through loop to add that product to that box if (packedBoxesIteration.IsEmpty() && CreateBoxesForOversizedItems) { // TODO: What should the empty box weight be? var oversizedItem = Items.GetMax(); var box = new Box() { Description = String.Format("Custom box for {0}", oversizedItem.Description), GeneratedId = Guid.NewGuid().ToString(), EmptyWeight = 0, InnerDepth = oversizedItem.Depth, InnerLength = oversizedItem.Length, InnerWidth = oversizedItem.Width, MaxWeight = oversizedItem.Weight, OuterDepth = oversizedItem.Depth, OuterLength = oversizedItem.Length, OuterWidth = oversizedItem.Width }; Boxes.Insert(box); _logger.Log(LogLevel.Debug, "Item {0} (id: {1}) is too large to fit into any box, creating custom box for it.", oversizedItem.Description, oversizedItem.Id); } else { PackedBox bestBox; if (!StartWithLargerBoxes) { bestBox = packedBoxesIteration.GetMin(); } else { var packedBoxListToEvaluate = packedBoxesIteration.GetContent().Cast <PackedBox>().ToList(); packedBoxListToEvaluate.Sort(packedBoxesIteration.ReverseCompareTo); bestBox = packedBoxListToEvaluate.FirstOrDefault(); } var bestBoxItems = bestBox.GetItems().ShallowCopy(); var unpackedItems = Items.GetContent().Cast <Item>().ToList(); foreach (var packedItem in bestBoxItems.GetContent()) { foreach (var unpackedItem in unpackedItems) { if (packedItem == unpackedItem) { unpackedItems.Remove(unpackedItem); break; } } } var unpackedItemList = new ItemList(); foreach (var unpackedItem in unpackedItems) { unpackedItemList.Insert(unpackedItem); } Items = unpackedItemList; packedBoxes.Insert(bestBox); } } return(packedBoxes); }