Just interested if could solve it with the lucene method…
Instead of words/terms have product_ids or skus, and instead of term frequencies have quantities.
Here’s is a proof concept… ish.
The theory/math needs going over as done it from memory.
But given a list of packing solutions, (ie all combinations of products than can fit into a particular package) and precomputed their vectors…
<?php
class Vector
{
static function dotProduct(array $a, array $b)
{
$r = 0;
foreach($a as $i => $ai)
if (isset($b[$i]))
$r += $ai * $b[$i];
return $r;
}
static function length(array $a)
{
return sqrt(array_reduce($a, function($r, $i) { return $r + ($i * $i); }, 0));
}
static function div(array $a, $n)
{
return array_map(function($i) use ($n) { return $i / $n; }, $a);
}
static function normalize(array $a)
{
return self::div($a, self::length($a));
}
}
class Packer
{
protected $solutions;
protected $vectors;
function __construct($solutions, $vectors)
{
$this->solutions = $solutions;
$this->vectors = $vectors;
}
function pack(array $productsToPack)
{
$solution = array();
while ($productsToPack)
{
$r = array();
$productsToPackVector = Vector::normalize($productsToPack);
echo 'Packing ', json_encode($productsToPack), "\
";
foreach($this->vectors as $solutionId => $vector)
{
$similarity = Vector::dotProduct($productsToPackVector, $vector);
echo ' -> ', json_encode($this->solutions[$solutionId]), ' ', $similarity, "\
";
if ($similarity > 0)
$r[$solutionId] = $similarity;
}
if ($r)
{
arsort($r);
list($partialSolution, $similarity) = each($r);
echo ' Packed using ', json_encode($this->solutions[$partialSolution]), ' ', $similarity, "\
";
foreach($this->solutions[$partialSolution] as $product => $quantity)
if (isset($productsToPack[$product]))
{
$productsToPack[$product] -= $quantity;
if (0 >= $productsToPack[$product])
unset($productsToPack[$product]);
}
$solution[] = $partialSolution;
}
else
throw new Exception('Unable to pack '.json_encode($productsToPack));
}
return $solution;
}
}
$products = array(
'orange',
'apple',
'banana',
);
$numberOfProducts = count($products);
$packingSolutions = array();
$packingSolutions[0] = array('orange' => 1); // can fit 1 orange in
$packingSolutions[1] = array('orange' => 4); // can fit 4 oranges in
$packingSolutions[2] = array('apple' => 1); // can fit 1 apple in
$packingSolutions[3] = array('apple' => 3); // can fit 3 apples in
$packingSolutions[4] = array('banana' => 1); // can fit 1 banana in
$packingSolutions[5] = array('orange' => 2, 'apple' => 2); // can fit 2 oranges & 2 apples in
$packingVectors = array();
foreach($packingSolutions as $packingSolutionId => $packedProducts)
$packingVectors[$packingSolutionId] = Vector::normalize($packedProducts);
$packer = new Packer($packingSolutions, $packingVectors);
$basket = array();
$basket['orange'] = 2;
$basket['apple'] = 3;
$solution = $packer->pack($basket);
echo "\
", 'Packing solution', "\
";
foreach($solution as $solutionId)
echo json_encode($packingSolutions[$solutionId]), "\
";
PS the math definitely needs checking…