Skip to content

Commit

Permalink
Add callback for more complex constraints. Fixes #60
Browse files Browse the repository at this point in the history
  • Loading branch information
dvdoug committed Apr 9, 2017
1 parent 59d32c0 commit 97c472b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 2 deletions.
29 changes: 29 additions & 0 deletions ConstrainedItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* Box packing (3D bin packing, knapsack problem)
* @package BoxPacker
* @author Doug Wright
*/
namespace DVDoug\BoxPacker;

/**
* An item to be packed where additional constraints need to be considered. Only implement this interface if you actually
* need this additional functionality as it will slow down the packing algorithm
* @author Doug Wright
* @package BoxPacker
*/
interface ConstrainedItem extends Item
{

/**
* Hook for user implementation of item-specific constraints, e.g. max <x> batteries per box
*
* @param ItemList $alreadyPackedItems
* @param Box $box
*
* @return bool
*/
public function canBePackedInBox(ItemList $alreadyPackedItems, Box $box);

}

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ seconds on my workstation, giving a rate of 220+ solutions/second which should b
most e-commerce stores :) If you do wish to benchmark the library to evaluate performance in your own scenarios, please
disable Xdebug when doing so - in my experience the unit tests take 24x longer (41 sec->16 min) when Xdebug is loaded.

Advanced Usage
--------------
For more advanced use cases where greater control over the contents of each box is required (e.g. legal limits on the
number of hazardous items per box), you may implement the `BoxPacker\ConstrainedItem` interface which contains an
additional callback method allowing you to decide whether an item may be packed into a box given it's existing contents.

Requirements
------------

Expand Down
27 changes: 25 additions & 2 deletions VolumePacker.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class VolumePacker implements LoggerAwareInterface

/**
* Constructor
*
* @param Box $box
* @param ItemList $items
*/
public function __construct(Box $box, ItemList $items)
{
Expand Down Expand Up @@ -108,12 +111,12 @@ public function pack()
$itemToPack = $this->items->extract();

//skip items that are simply too heavy
if ($itemToPack->getWeight() > $this->remainingWeight) {
if (!$this->checkNonDimensionalConstraints($itemToPack, $packedItems)) {
continue;
}

$this->logger->debug(
"evaluating item {$itemToPack->getDescription()}",
"evaluating item {$itemToPack->getDescription()} for fit",
[
'item' => $itemToPack,
'space' => [
Expand Down Expand Up @@ -247,4 +250,24 @@ protected function isLayerStarted($layerWidth, $layerLength, $layerDepth)
{
return $layerWidth > 0 && $layerLength > 0 && $layerDepth > 0;
}

/**
* As well as purely dimensional constraints, there are other constraints that need to be met
* e.g. weight limits or item-specific restrictions (e.g. max <x> batteries per box)
*
* @param Item $itemToPack
* @param ItemList $packedItems
*
* @return bool
*/
protected function checkNonDimensionalConstraints(Item $itemToPack, ItemList $packedItems)
{
$weightOK = $itemToPack->getWeight() <= $this->remainingWeight;

if ($itemToPack instanceof ConstrainedItem) {
return $weightOK && $itemToPack->canBePackedInBox(clone $packedItems, $this->box);
}

return $weightOK;
}
}
12 changes: 12 additions & 0 deletions tests/VolumePackerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,16 @@ public function testIssue53()

self::assertEquals(1, $packedBoxes->count());
}

public function testConstraints()
{
TestConstrainedItem::$limit = 2;

$packer = new Packer();
$packer->addBox(new TestBox('Box', 10, 10, 10, 0, 10, 10, 10, 0));
$packer->addItem(new TestConstrainedItem('Item', 1, 1, 1, 0, false), 8);
$packedBoxes = $packer->pack();

self::assertEquals(4, $packedBoxes->count());
}
}
17 changes: 17 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,20 @@ public function getKeepFlat()
}
}

class TestConstrainedItem extends TestItem implements ConstrainedItem
{
public static $limit = 3;

public function canBePackedInBox(ItemList $alreadyPackedItems, Box $box)
{
$alreadyPackedType = array_filter(
$alreadyPackedItems->asArray(),
function(Item $item) {
return $item->getDescription() === $this->getDescription();
}
);

return count($alreadyPackedType) + 1 <= static::$limit;
}
}

0 comments on commit 97c472b

Please sign in to comment.