Below are two warehouses and three stores. Each warehouse has a supply; each store has a demand; each cell holds the cost to ship one crate along that route. Edit any number, then press Solve to see the cheapest plan that satisfies every store.
<p class="hint">Two warehouses (rows) supply three stores (columns). Each cell is the cost to ship one crate along that route. Edit any number, then press <b>Solve</b> — the minimum-cost shipping plan appears instantly. Supply and demand are balanced for you.</p>
<div class="grid" id="grid"></div>
<div class="status" id="status">Edit the numbers, then Solve.</div>
<div class="btns">
<button id="solve" type="button">Solve</button>
<button id="rand" type="button" class="ghost">Randomize costs</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.grid { display: grid; grid-template-columns: 90px repeat(3, 1fr) 70px; gap: 6px; align-items: stretch; }
.h { font: 700 13px system-ui; display: flex; align-items: center; justify-content: center;
color: #1d3557; text-align: center; }
.lab { font: 700 13px system-ui; display: flex; align-items: center; color: #1d3557; }
.cell { position: relative; background: #eef3f7; border: 1px solid #cdd9e3; border-radius: 8px;
min-height: 54px; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.cell input { width: 48px; text-align: center; font: 700 15px ui-monospace, monospace; border: none;
background: transparent; color: #1d3557; }
.cell input:focus { outline: 2px solid #457b9d; border-radius: 4px; }
.ship { font: 700 12px system-ui; margin-top: 2px; color: #0a7d33; min-height: 14px; }
.ship.empty { color: #9aa6b1; }
.sd { background: #1d3557; color: #fff; border-radius: 8px; display: flex; align-items: center;
justify-content: center; font: 700 14px ui-monospace, monospace; }
.sd input { width: 40px; text-align: center; font: 700 14px ui-monospace, monospace; border: none;
background: transparent; color: #fff; }
.status { font-size: 1rem; font-weight: 600; margin: .8rem 0 .5rem; min-height: 1.4em; color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Transportation problem: ship from sources (supplies) to sinks (demands) at minimum total cost. It is a linear program with network structure, solved in polynomial time by the network simplex. Here m=2, n=3 is tiny, so we compute the true optimum by enumerating basic feasible solutions — the cost function is linear, so the minimum is always attained at such a vertex.
const M = 2, N = 3;
let cost = [[4, 6, 8], [9, 5, 3]];
let supply = [12, 8]; // total 20
let demand = [7, 6, 7]; // total 20
// Enumerate every integer shipment matrix consistent with supply/demand and return the cheapest. Feasible region is small; this is an exact LP solve.
function solve() {
const total = supply.reduce((a, b) => a + b, 0);
let best = null, bestCost = Infinity;
// Choose x[0][0], x[0][1] freely (0..supply[0]); the rest is forced by the balance equations because m=2, n=3 leaves m*n - (m+n-1) = 2 free cells.
for (let a = 0; a <= supply[0]; a++) {
for (let b = 0; b <= supply[0] - a; b++) {
const x = [[a, b, supply[0] - a - b], [0, 0, 0]];
x[1][0] = demand[0] - x[0][0];
x[1][1] = demand[1] - x[0][1];
x[1][2] = demand[2] - x[0][2];
if (x[1][0] < 0 || x[1][1] < 0 || x[1][2] < 0) continue;
// (row-1 sum already equals supply[1] by balance: total - supply[0])
let c = 0;
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) c += x[i][j] * cost[i][j];
if (c < bestCost) { bestCost = c; best = x.map(r => r.slice()); }
}
}
return { plan: best, cost: bestCost };
}
function buildGrid() {
const g = document.getElementById('grid'); g.innerHTML = '';
const mk = (cls, html) => { const d = document.createElement('div'); d.className = cls; d.innerHTML = html; return d; };
g.appendChild(mk('h', ''));
for (let j = 0; j < N; j++) g.appendChild(mk('h', fmt('Store %j%%v%', { j: j, v: 1 })));
g.appendChild(mk('h', 'Supply'));
for (let i = 0; i < M; i++) {
g.appendChild(mk('lab', fmt('Warehouse %i%%v%', { i: i, v: 1 })));
for (let j = 0; j < N; j++) {
const cell = document.createElement('div'); cell.className = 'cell';
const inp = document.createElement('input');
inp.type = 'number'; inp.min = '0'; inp.value = cost[i][j];
inp.addEventListener('change', () => { cost[i][j] = Math.max(0, parseInt(inp.value) || 0); inp.value = cost[i][j]; clear(); });
const ship = document.createElement('div'); ship.className = 'ship empty'; ship.id = 's' + i + j;
cell.appendChild(inp); cell.appendChild(ship); g.appendChild(cell);
}
const sd = document.createElement('div'); sd.className = 'sd';
const si = document.createElement('input'); si.type = 'number'; si.min = '0'; si.value = supply[i];
si.addEventListener('change', () => { supply[i] = Math.max(0, parseInt(si.value) || 0); rebalance(); });
sd.appendChild(si); g.appendChild(sd);
}
g.appendChild(mk('lab', 'Demand'));
for (let j = 0; j < N; j++) {
const sd = document.createElement('div'); sd.className = 'sd';
const di = document.createElement('input'); di.type = 'number'; di.min = '0'; di.value = demand[j];
di.addEventListener('change', () => { demand[j] = Math.max(0, parseInt(di.value) || 0); rebalance(); });
sd.appendChild(di); g.appendChild(sd);
}
g.appendChild(mk('h', ''));
}
// Keep total supply == total demand by nudging the last demand cell.
function rebalance() {
const ts = supply.reduce((a, b) => a + b, 0);
let td = demand.reduce((a, b) => a + b, 0);
const diff = ts - td;
demand[N - 1] = Math.max(0, demand[N - 1] + diff);
buildGrid(); clear();
}
function clear() {
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) {
const s = document.getElementById('s' + i + j); s.textContent = ''; s.className = 'ship empty';
}
const st = document.getElementById('status');
st.textContent = 'Edit the numbers, then Solve.';
}
function run() {
const { plan, cost: c } = solve();
if (!plan) { document.getElementById('status').textContent = 'Supply and demand do not match.'; return; }
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) {
const s = document.getElementById('s' + i + j);
s.textContent = plan[i][j] > 0 ? (fmt('ship %v%', { v: plan[i][j] })) : '·';
s.className = plan[i][j] > 0 ? 'ship' : 'ship empty';
}
document.getElementById('status').textContent = fmt('Minimum total cost: %c%', { c: c });
console.log('Optimal plan:', JSON.stringify(plan), '| cost', c);
}
function randomize() {
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) cost[i][j] = 1 + Math.floor(Math.random() * 9);
buildGrid(); clear();
}
document.getElementById('solve').addEventListener('click', run);
document.getElementById('rand').addEventListener('click', randomize);
buildGrid();
<p class="hint">Two warehouses (rows) supply three stores (columns). Each cell is the cost to ship one crate along that route. Edit any number, then press <b>Solve</b> — the minimum-cost shipping plan appears instantly. Supply and demand are balanced for you.</p>
<div class="grid" id="grid"></div>
<div class="status" id="status">Edit the numbers, then Solve.</div>
<div class="btns">
<button id="solve" type="button">Solve</button>
<button id="rand" type="button" class="ghost">Randomize costs</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.grid { display: grid; grid-template-columns: 90px repeat(3, 1fr) 70px; gap: 6px; align-items: stretch; }
.h { font: 700 13px system-ui; display: flex; align-items: center; justify-content: center;
color: #1d3557; text-align: center; }
.lab { font: 700 13px system-ui; display: flex; align-items: center; color: #1d3557; }
.cell { position: relative; background: #eef3f7; border: 1px solid #cdd9e3; border-radius: 8px;
min-height: 54px; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.cell input { width: 48px; text-align: center; font: 700 15px ui-monospace, monospace; border: none;
background: transparent; color: #1d3557; }
.cell input:focus { outline: 2px solid #457b9d; border-radius: 4px; }
.ship { font: 700 12px system-ui; margin-top: 2px; color: #0a7d33; min-height: 14px; }
.ship.empty { color: #9aa6b1; }
.sd { background: #1d3557; color: #fff; border-radius: 8px; display: flex; align-items: center;
justify-content: center; font: 700 14px ui-monospace, monospace; }
.sd input { width: 40px; text-align: center; font: 700 14px ui-monospace, monospace; border: none;
background: transparent; color: #fff; }
.status { font-size: 1rem; font-weight: 600; margin: .8rem 0 .5rem; min-height: 1.4em; color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Transportation problem: ship from sources (supplies) to sinks (demands) at minimum total cost. It is a linear program with network structure, solved in polynomial time by the network simplex. Here m=2, n=3 is tiny, so we compute the true optimum by enumerating basic feasible solutions — the cost function is linear, so the minimum is always attained at such a vertex.
const M = 2, N = 3;
let cost = [[4, 6, 8], [9, 5, 3]];
let supply = [12, 8]; // total 20
let demand = [7, 6, 7]; // total 20
// Enumerate every integer shipment matrix consistent with supply/demand and return the cheapest. Feasible region is small; this is an exact LP solve.
function solve() {
const total = supply.reduce((a, b) => a + b, 0);
let best = null, bestCost = Infinity;
// Choose x[0][0], x[0][1] freely (0..supply[0]); the rest is forced by the balance equations because m=2, n=3 leaves m*n - (m+n-1) = 2 free cells.
for (let a = 0; a <= supply[0]; a++) {
for (let b = 0; b <= supply[0] - a; b++) {
const x = [[a, b, supply[0] - a - b], [0, 0, 0]];
x[1][0] = demand[0] - x[0][0];
x[1][1] = demand[1] - x[0][1];
x[1][2] = demand[2] - x[0][2];
if (x[1][0] < 0 || x[1][1] < 0 || x[1][2] < 0) continue;
// (row-1 sum already equals supply[1] by balance: total - supply[0])
let c = 0;
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) c += x[i][j] * cost[i][j];
if (c < bestCost) { bestCost = c; best = x.map(r => r.slice()); }
}
}
return { plan: best, cost: bestCost };
}
function buildGrid() {
const g = document.getElementById('grid'); g.innerHTML = '';
const mk = (cls, html) => { const d = document.createElement('div'); d.className = cls; d.innerHTML = html; return d; };
g.appendChild(mk('h', ''));
for (let j = 0; j < N; j++) g.appendChild(mk('h', fmt('Store %j%%v%', { j: j, v: 1 })));
g.appendChild(mk('h', 'Supply'));
for (let i = 0; i < M; i++) {
g.appendChild(mk('lab', fmt('Warehouse %i%%v%', { i: i, v: 1 })));
for (let j = 0; j < N; j++) {
const cell = document.createElement('div'); cell.className = 'cell';
const inp = document.createElement('input');
inp.type = 'number'; inp.min = '0'; inp.value = cost[i][j];
inp.addEventListener('change', () => { cost[i][j] = Math.max(0, parseInt(inp.value) || 0); inp.value = cost[i][j]; clear(); });
const ship = document.createElement('div'); ship.className = 'ship empty'; ship.id = 's' + i + j;
cell.appendChild(inp); cell.appendChild(ship); g.appendChild(cell);
}
const sd = document.createElement('div'); sd.className = 'sd';
const si = document.createElement('input'); si.type = 'number'; si.min = '0'; si.value = supply[i];
si.addEventListener('change', () => { supply[i] = Math.max(0, parseInt(si.value) || 0); rebalance(); });
sd.appendChild(si); g.appendChild(sd);
}
g.appendChild(mk('lab', 'Demand'));
for (let j = 0; j < N; j++) {
const sd = document.createElement('div'); sd.className = 'sd';
const di = document.createElement('input'); di.type = 'number'; di.min = '0'; di.value = demand[j];
di.addEventListener('change', () => { demand[j] = Math.max(0, parseInt(di.value) || 0); rebalance(); });
sd.appendChild(di); g.appendChild(sd);
}
g.appendChild(mk('h', ''));
}
// Keep total supply == total demand by nudging the last demand cell.
function rebalance() {
const ts = supply.reduce((a, b) => a + b, 0);
let td = demand.reduce((a, b) => a + b, 0);
const diff = ts - td;
demand[N - 1] = Math.max(0, demand[N - 1] + diff);
buildGrid(); clear();
}
function clear() {
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) {
const s = document.getElementById('s' + i + j); s.textContent = ''; s.className = 'ship empty';
}
const st = document.getElementById('status');
st.textContent = 'Edit the numbers, then Solve.';
}
function run() {
const { plan, cost: c } = solve();
if (!plan) { document.getElementById('status').textContent = 'Supply and demand do not match.'; return; }
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) {
const s = document.getElementById('s' + i + j);
s.textContent = plan[i][j] > 0 ? (fmt('ship %v%', { v: plan[i][j] })) : '·';
s.className = plan[i][j] > 0 ? 'ship' : 'ship empty';
}
document.getElementById('status').textContent = fmt('Minimum total cost: %c%', { c: c });
console.log('Optimal plan:', JSON.stringify(plan), '| cost', c);
}
function randomize() {
for (let i = 0; i < M; i++) for (let j = 0; j < N; j++) cost[i][j] = 1 + Math.floor(Math.random() * 9);
buildGrid(); clear();
}
document.getElementById('solve').addEventListener('click', run);
document.getElementById('rand').addEventListener('click', randomize);
buildGrid();
Notice what happens. The space of possible plans is enormous, yet the optimal one snaps into place instantly. That is because the transportation problem is a linear program with a special network structure — and structure like this is exactly what makes a problem tractable instead of a brute-force nightmare. (Total supply must equal total demand; the demo balances it for you.)
Comments
Loading comments...