Guarda cosa hai scritto:
They request 20 pieces.
And the distribution is as follows '4'=>5, '1'=>5, '2'=>1, '10'=>8, '9'=>10.
So ideally the warehouses will be assigned something like this: WID:4=5, WID:1=5, WID:9=10.
Perché non c'è WID: 2 = 1, o del resto no WID: 10 = 8? Qui devi utilizzare una logica, che ti fa dire "Ottenere 1 prodotto dal magazzino 2 costa di più di ...". Questa è la tua funzione di costo .
Un approccio a forza bruta avrebbe una funzione che, dato il layout del magazzino e l'allocazione del magazzino, determina il costo dell'ordine:
function costEstimate(array $wh, array $order) {
// assert('count($wh) == count($order)');
$cost = 0;
// If we proceeded with $order, $wh levels would become
$whAfter = array_map(function($whStock, $whOrdered) {
// assert this is not negative
return $whStock - $whOrdered;
}, $wh, $order);
// Number of shipments is number of non-zero requisitions
$cost += count(array_filter($order)) * COST_PER_SHIPMENT;
// We do not want to leave a magazine with less than low reorder threshold
$isBelowThreshold = function($stock) { return $stock < REORDER_THRESHOLD; };
$reorder_before = count(array_filter($wh, $isBelowThreshold));
$reorder_after = count(array_filter($whAfter), $isBelowThreshold));
$cost += ($reorder_after - $reorder_before) * COST_PER_REORDER;
// But if we manage to completely empty a warehouse that's good
$empty_before = count(array_filter($wh));
$empty_after = count(array_filter($whAfter));
$cost -= ($empty_after - $empty_before) * GAIN_PER_EMPTYING;
// Now add costs per distance
$idx = 0;
foreach ($order as $whId => $whNum) {
$cost += $whNum * $idx * COST_PER_UNIT_PER_DISTANCE;
$idx++;
}
return $cost;
}
Puoi aggiungere ancora più criteri al costo o più dettagliati (ad esempio la distanza effettiva del magazzino o i costi di spedizione effettivi anziché la classifica). Oppure puoi aggiungere una funzione gamma a $ idx nel ciclo precedente per privilegiare i magazzini più vicini piuttosto che quelli più lontani.
Una volta che hai questo, il tuo obiettivo è trovare la combinazione (il vettore $ ordine) che minimizza il tuo costo. Puoi (ricorda - questo è l'approccio forza bruta .Per ordini di grandi dimensioni o ordini molto frequenti dovrai cercare ricerca delle operazioni , o almeno i vari aspetti del problema dello zaino) prova tutte le possibili combinazioni di ordini. Questo va rapidamente fuori controllo:
foreach ($combinations as $comb) {
$test = costEstimate($wh, $comb);
if ($test < $testSoFar) {
$best = $comb;
$test = $testSoFar;
}
}
A seconda della funzione di costo, potresti essere in grado di migliorare l'algoritmo "sfoltendo" le opzioni poco promettenti.
Un'altra regola
Ad esempio, vuoi svuotare tutti i primi magazzini, a meno che non ci sia un magazzino che soddisfi completamente l'ordine rimanente. In tal caso non hai bisogno di una funzione di costo esplicita:
while ($totalOrder) {
// Test 1. Can we satisfy this entirely?
foreach ($warehouses as $id => $quantity) {
if ($quantity >= $totalOrder) {
$assignment[$id] = $totalOrder;
$totalOrder = 0;
break;
}
}
// Test 2. Do we still need to requisition items?
if ($totalOrder > 0) {
// Use the first suitable warehouse.
foreach ($warehouses as $id => $quantity) {
if ($quantity > 0) {
$assignment[$id] = $quantity;
$totalOrder -= $quantity;
// Stop this cycle and repeat.
break;
}
}
}
}
La funzione sopra descritta, nel tuo esempio, cercherà di inviare 20 pezzi. Nessun magazzino ha 20 pezzi, quindi il primo test fallisce. Il primo WH non vuoto è quindi 4, che viene assegnato 5. Il ciclo continua e tenta di inviare 15 e non può, quindi i magazzini sono esaminati di nuovo (array_shift e array_filters qui potrebbero essere i tuoi amici), e il magazzino 1 è selezionato per ottenere 5 pz. Il resto è ora 10 e l'ultimo magazzino può soddisfarlo.
L'assegnazione finale con questo algoritmo sarà WID: 4 = 5, WID: 1 = 5, WID: 9 = 10 e l'operazione è O (N 2 ) nel numero di magazzini. Puoi ottenere O (N log N) mantenendo un array ruotato in ordine di dimensioni, ma per valori realistici di N non devi preoccuparti.